diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index 3146403ac6b..eec164ce822 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -91,7 +91,7 @@ def __init__( When set to True, a default list will be used. We strongly recommend using docker for code execution. - timeout (Optional, int): The maximum execution time in seconds. - - last_n_messages (Experimental, Optional, int): The number of messages to look back for code execution. Default to 1. + - last_n_messages (Experimental, Optional, int or str): The number of messages to look back for code execution. Default to 1. If set to 'auto', it will scan backwards through all messages arriving since the agent last spoke (typically this is the last time execution was attempted). llm_config (dict or False): llm inference configuration. Please refer to [OpenAIWrapper.create](/docs/reference/oai/client#create) for available options. @@ -635,10 +635,23 @@ def generate_code_execution_reply( messages = self._oai_messages[sender] last_n_messages = code_execution_config.pop("last_n_messages", 1) + messages_to_scan = last_n_messages + if last_n_messages == "auto": + # Find when the agent last spoke + messages_to_scan = 0 + for i in range(len(messages)): + message = messages[-(i + 1)] + if "role" not in message: + break + elif message["role"] != "user": + break + else: + messages_to_scan += 1 + # iterate through the last n messages reversly # if code blocks are found, execute the code blocks and return the output # if no code blocks are found, continue - for i in range(min(len(messages), last_n_messages)): + for i in range(min(len(messages), messages_to_scan)): message = messages[-(i + 1)] if not message["content"]: continue diff --git a/test/agentchat/test_conversable_agent.py b/test/agentchat/test_conversable_agent.py index 4ba92cbc4c6..d38193338f4 100644 --- a/test/agentchat/test_conversable_agent.py +++ b/test/agentchat/test_conversable_agent.py @@ -128,6 +128,71 @@ def test_generate_code_execution_reply(): ) assert agent._code_execution_config["last_n_messages"] == 3 + # scenario 5: if last_n_messages is set to 'auto' and no code is found, then nothing breaks both when an assistant message is and isn't present + assistant_message_for_auto = { + "content": "This is me! The assistant!", + "role": "assistant", + } + + dummy_messages_for_auto = [] + for i in range(3): + dummy_messages_for_auto.append( + { + "content": "no code block", + "role": "user", + } + ) + + # Without an assistant present + agent._code_execution_config = {"last_n_messages": "auto", "use_docker": False} + assert agent.generate_code_execution_reply(dummy_messages_for_auto) == ( + False, + None, + ) + + # With an assistant message present + agent._code_execution_config = {"last_n_messages": "auto", "use_docker": False} + assert agent.generate_code_execution_reply([assistant_message_for_auto] + dummy_messages_for_auto) == ( + False, + None, + ) + + # scenario 6: if last_n_messages is set to 'auto' and code is found, then we execute it correctly + dummy_messages_for_auto = [] + for i in range(4): + # Without an assistant present + agent._code_execution_config = {"last_n_messages": "auto", "use_docker": False} + assert agent.generate_code_execution_reply([code_message] + dummy_messages_for_auto) == ( + True, + "exitcode: 0 (execution succeeded)\nCode output: \nhello world\n", + ) + + # With an assistant message present + agent._code_execution_config = {"last_n_messages": "auto", "use_docker": False} + assert agent.generate_code_execution_reply( + [assistant_message_for_auto] + [code_message] + dummy_messages_for_auto + ) == ( + True, + "exitcode: 0 (execution succeeded)\nCode output: \nhello world\n", + ) + + dummy_messages_for_auto.append( + { + "content": "no code block", + "role": "user", + } + ) + + # scenario 7: if last_n_messages is set to 'auto' and code is present, but not before an assistant message, then nothing happens + agent._code_execution_config = {"last_n_messages": "auto", "use_docker": False} + assert agent.generate_code_execution_reply( + [code_message] + [assistant_message_for_auto] + dummy_messages_for_auto + ) == ( + False, + None, + ) + assert agent._code_execution_config["last_n_messages"] == "auto" + def test_max_consecutive_auto_reply(): agent = ConversableAgent("a0", max_consecutive_auto_reply=2, llm_config=False, human_input_mode="NEVER") @@ -249,4 +314,5 @@ async def test_a_generate_reply_raises_on_messages_and_sender_none(conversable_a # test_trigger() # test_context() # test_max_consecutive_auto_reply() + # test_generate_code_execution_reply() test_conversable_agent()