From cd9fca571394b8ef781f065536f7af95cbc3722e Mon Sep 17 00:00:00 2001 From: Charles Packer Date: Tue, 21 Nov 2023 19:15:28 -0800 Subject: [PATCH] Fixes bugs with AutoGen implementation and exampes (#498) * patched bugs in autogen agent example, updated autogen agent creation to follow agentconfig paradigm * more fixes * black * fix bug in autoreply * black * pass default autoreply through to the memgpt autogen conversibleagent subclass so that it doesn't leave empty messages which can trigger errors in local llm backends like lmstudio --- memgpt/autogen/examples/agent_autoreply.py | 72 ++++++++++-- memgpt/autogen/examples/agent_docs.py | 102 ++++++++++------- memgpt/autogen/examples/agent_groupchat.py | 124 ++++++++++----------- memgpt/autogen/memgpt_agent.py | 91 ++++++++------- memgpt/cli/cli.py | 1 + 5 files changed, 241 insertions(+), 149 deletions(-) diff --git a/memgpt/autogen/examples/agent_autoreply.py b/memgpt/autogen/examples/agent_autoreply.py index d3b4fce2ff..e8a39a213c 100644 --- a/memgpt/autogen/examples/agent_autoreply.py +++ b/memgpt/autogen/examples/agent_autoreply.py @@ -12,13 +12,58 @@ import os import autogen from memgpt.autogen.memgpt_agent import create_memgpt_autogen_agent_from_config +from memgpt.presets.presets import DEFAULT_PRESET + +# USE_OPENAI = True +USE_OPENAI = False +if USE_OPENAI: + # This config is for autogen agents that are not powered by MemGPT + config_list = [ + { + "model": "gpt-4-1106-preview", # gpt-4-turbo (https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo) + "api_key": os.getenv("OPENAI_API_KEY"), + } + ] + + # This config is for autogen agents that powered by MemGPT + config_list_memgpt = [ + { + "model": "gpt-4-1106-preview", # gpt-4-turbo (https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo) + "preset": "memgpt_docs", + "model": None, + "model_wrapper": None, + "model_endpoint_type": None, + "model_endpoint": None, + "context_window": 128000, # gpt-4-turbo + }, + ] + +else: + # Example using LM Studio on a local machine + # You will have to change the parameters based on your setup + + # Non-MemGPT agents will still use local LLMs, but they will use the ChatCompletions endpoint + config_list = [ + { + "model": "NULL", # not needed + "api_base": "http://localhost:1234/v1", # ex. "http://127.0.0.1:5001/v1" if you are using webui, "http://localhost:1234/v1/" if you are using LM Studio + "api_key": "NULL", # not needed + "api_type": "open_ai", + }, + ] + + # MemGPT-powered agents will also use local LLMs, but they need additional setup (also they use the Completions endpoint) + config_list_memgpt = [ + { + "preset": DEFAULT_PRESET, + "model": None, # only required for Ollama, see: https://memgpt.readthedocs.io/en/latest/ollama/ + "model_wrapper": "airoboros-l2-70b-2.1", # airoboros is the default wrapper and should work for most models + "model_endpoint_type": "lmstudio", # can use webui, ollama, llamacpp, etc. + "model_endpoint": "http://localhost:1234", # the IP address of your LLM backend + "context_window": 8192, # the context window of your model (for Mistral 7B-based models, it's likely 8192) + }, + ] -config_list = [ - { - "model": "gpt-4", - "api_key": os.getenv("OPENAI_API_KEY"), - }, -] # If USE_MEMGPT is False, then this example will be the same as the official AutoGen repo # (https://github.com/microsoft/autogen/blob/main/notebook/agentchat_groupchat.ipynb) @@ -26,6 +71,15 @@ USE_MEMGPT = True llm_config = {"config_list": config_list, "seed": 42} +llm_config_memgpt = {"config_list": config_list_memgpt, "seed": 42} + +# Set to True if you want to print MemGPT's inner workings. +DEBUG = False +interface_kwargs = { + "debug": DEBUG, + "show_inner_thoughts": DEBUG, + "show_function_outputs": DEBUG, +} # The user agent user_proxy = autogen.UserProxyAgent( @@ -45,6 +99,7 @@ system_message=f"I am a 10x engineer, trained in Python. I was the first engineer at Uber " f"(which I make sure to tell everyone I work with).", human_input_mode="TERMINATE", + default_auto_reply="...", # Set a default auto-reply message here (non-empty auto-reply is required for LM Studio) ) else: @@ -52,10 +107,13 @@ # This MemGPT agent will have all the benefits of MemGPT, ie persistent memory, etc. coder = create_memgpt_autogen_agent_from_config( "MemGPT_coder", - llm_config=llm_config, + llm_config=llm_config_memgpt, + nonmemgpt_llm_config=llm_config, system_message=f"I am a 10x engineer, trained in Python. I was the first engineer at Uber " f"(which I make sure to tell everyone I work with).", human_input_mode="TERMINATE", + interface_kwargs=interface_kwargs, + default_auto_reply="...", # Set a default auto-reply message here (non-empty auto-reply is required for LM Studio) ) # Begin the group chat with a message from the user diff --git a/memgpt/autogen/examples/agent_docs.py b/memgpt/autogen/examples/agent_docs.py index b090b96823..090155bfde 100644 --- a/memgpt/autogen/examples/agent_docs.py +++ b/memgpt/autogen/examples/agent_docs.py @@ -16,22 +16,55 @@ import autogen from memgpt.autogen.memgpt_agent import create_autogen_memgpt_agent, create_memgpt_autogen_agent_from_config -# This config is for autogen agents that are not powered by MemGPT -config_list = [ - { - "model": "gpt-4", - "api_key": os.getenv("OPENAI_API_KEY"), - } -] - -# This config is for autogen agents that powered by MemGPT -config_list_memgpt = [ - { - "model": "gpt-4", - }, -] - -USE_AUTOGEN_WORKFLOW = True +# USE_OPENAI = True +USE_OPENAI = False +if USE_OPENAI: + # This config is for autogen agents that are not powered by MemGPT + config_list = [ + { + "model": "gpt-4-1106-preview", # gpt-4-turbo (https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo) + "api_key": os.getenv("OPENAI_API_KEY"), + } + ] + + # This config is for autogen agents that powered by MemGPT + config_list_memgpt = [ + { + "model": "gpt-4-1106-preview", # gpt-4-turbo (https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo) + "preset": "memgpt_docs", + "model": None, + "model_wrapper": None, + "model_endpoint_type": None, + "model_endpoint": None, + "context_window": 128000, # gpt-4-turbo + }, + ] + +else: + # Example using LM Studio on a local machine + # You will have to change the parameters based on your setup + + # Non-MemGPT agents will still use local LLMs, but they will use the ChatCompletions endpoint + config_list = [ + { + "model": "NULL", # not needed + "api_base": "http://localhost:1234/v1", # ex. "http://127.0.0.1:5001/v1" if you are using webui, "http://localhost:1234/v1/" if you are using LM Studio + "api_key": "NULL", # not needed + "api_type": "open_ai", + }, + ] + + # MemGPT-powered agents will also use local LLMs, but they need additional setup (also they use the Completions endpoint) + config_list_memgpt = [ + { + "preset": "memgpt_docs", + "model": None, # only required for Ollama, see: https://memgpt.readthedocs.io/en/latest/ollama/ + "model_wrapper": "airoboros-l2-70b-2.1", # airoboros is the default wrapper and should work for most models + "model_endpoint_type": "lmstudio", # can use webui, ollama, llamacpp, etc. + "model_endpoint": "http://localhost:1234", # the IP address of your LLM backend + "context_window": 8192, # the context window of your model (for Mistral 7B-based models, it's likely 8192) + }, + ] # Set to True if you want to print MemGPT's inner workings. DEBUG = False @@ -56,30 +89,21 @@ # In our example, we swap this AutoGen agent with a MemGPT agent # This MemGPT agent will have all the benefits of MemGPT, ie persistent memory, etc. -if not USE_AUTOGEN_WORKFLOW: - coder = create_autogen_memgpt_agent( - "MemGPT_coder", - persona_description="I am a 10x engineer, trained in Python. I was the first engineer at Uber " - "(which I make sure to tell everyone I work with).", - user_description=f"You are participating in a group chat with a user ({user_proxy.name}) " f"and a product manager ({pm.name}).", - model=config_list_memgpt[0]["model"], - interface_kwargs=interface_kwargs, - ) -else: - coder = create_memgpt_autogen_agent_from_config( - "MemGPT_coder", - llm_config=llm_config_memgpt, - system_message=f"I am a 10x engineer, trained in Python. I was the first engineer at Uber " - f"(which I make sure to tell everyone I work with).\n" - f"You are participating in a group chat with a user ({user_proxy.name}).", - interface_kwargs=interface_kwargs, - ) - coder.attach("memgpt_research_paper") # See https://memgpt.readthedocs.io/en/latest/autogen/#loading-documents - -# Initialize the group chat between the user and two LLM agents (PM and coder) -groupchat = autogen.GroupChat(agents=[user_proxy, coder], messages=[], max_round=12) -manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=llm_config) +memgpt_agent = create_memgpt_autogen_agent_from_config( + "MemGPT_agent", + llm_config=llm_config_memgpt, + system_message=f"I am a 10x engineer, trained in Python. I was the first engineer at Uber " + f"(which I make sure to tell everyone I work with).\n" + f"You are participating in a group chat with a user ({user_proxy.name}).", + interface_kwargs=interface_kwargs, + default_auto_reply="...", # Set a default auto-reply message here (non-empty auto-reply is required for LM Studio) +) +# NOTE: you need to follow steps to load document first: see https://memgpt.readthedocs.io/en/latest/autogen/#loading-documents +memgpt_agent.load_and_attach("memgpt_research_paper", "directory") +# Initialize the group chat between the agents +groupchat = autogen.GroupChat(agents=[user_proxy, memgpt_agent], messages=[], max_round=12) +manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=llm_config) # Begin the group chat with a message from the user user_proxy.initiate_chat( diff --git a/memgpt/autogen/examples/agent_groupchat.py b/memgpt/autogen/examples/agent_groupchat.py index bcd73e0bcd..0104bc9491 100644 --- a/memgpt/autogen/examples/agent_groupchat.py +++ b/memgpt/autogen/examples/agent_groupchat.py @@ -13,55 +13,63 @@ import os import autogen from memgpt.autogen.memgpt_agent import create_autogen_memgpt_agent, create_memgpt_autogen_agent_from_config +from memgpt.presets.presets import DEFAULT_PRESET +from memgpt.constants import LLM_MAX_TOKENS + +USE_OPENAI = True +if USE_OPENAI: + # This config is for autogen agents that are not powered by MemGPT + config_list = [ + { + "model": "gpt-4-1106-preview", # gpt-4-turbo (https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo) + "api_key": os.getenv("OPENAI_API_KEY"), + } + ] + + # This config is for autogen agents that powered by MemGPT + config_list_memgpt = [ + { + "model": "gpt-4-1106-preview", # gpt-4-turbo (https://platform.openai.com/docs/models/gpt-4-and-gpt-4-turbo) + "preset": DEFAULT_PRESET, + "model": None, + "model_wrapper": None, + "model_endpoint_type": None, + "model_endpoint": None, + "context_window": 128000, # gpt-4-turbo + }, + ] -# This config is for autogen agents that are not powered by MemGPT -config_list = [ - { - "model": "gpt-4", - "api_key": os.getenv("OPENAI_API_KEY"), - } -] - -# This config is for autogen agents that powered by MemGPT -config_list_memgpt = [ - { - "model": "gpt-4", - }, -] - -# Uncomment and fill in the following for local LLM deployment: -# # This config is for autogen agents that are not powered by MemGPT -# # See https://github.com/oobabooga/text-generation-webui/tree/main/extensions/openai -config_list = [ - { - "model": "YOUR_MODEL", # ex. This is the model name, not the wrapper - "api_base": "YOUR_URL", # ex. "http://127.0.0.1:5001/v1" if you are using webui, "http://localhost:1234/v1/" if you are using LM Studio - "api_key": "NULL", # this is a placeholder - "api_type": "open_ai", - }, -] - -# # This config is for autogen agents that powered by MemGPT -# # For this to work, you need to have your environment variables set correctly, e.g. -# # For web UI: -# # OPENAI_API_BASE=http://127.0.0.1:5000 -# # BACKEND_TYPE=webui -# # For LM Studio: -# # OPENAI_API_BASE=http://127.0.0.1:1234 -# # BACKEND_TYPE=lmstudio -# # "model" here specifies the "wrapper" that will be used, setting it to "gpt-4" uses the default -config_list_memgpt = [ - {"model": "airoboros-l2-70b-2.1"}, # if you set this to gpt-4, it will fall back to the default wrapper -] - +else: + # Example using LM Studio on a local machine + # You will have to change the parameters based on your setup + + # Non-MemGPT agents will still use local LLMs, but they will use the ChatCompletions endpoint + config_list = [ + { + "model": "NULL", # not needed + "api_base": "http://localhost:1234/v1", # ex. "http://127.0.0.1:5001/v1" if you are using webui, "http://localhost:1234/v1/" if you are using LM Studio + "api_key": "NULL", # not needed + "api_type": "open_ai", + }, + ] + + # MemGPT-powered agents will also use local LLMs, but they need additional setup (also they use the Completions endpoint) + config_list_memgpt = [ + { + "preset": DEFAULT_PRESET, + "model": None, # only required for Ollama, see: https://memgpt.readthedocs.io/en/latest/ollama/ + "model_wrapper": "airoboros-l2-70b-2.1", # airoboros is the default wrapper and should work for most models + "model_endpoint_type": "lmstudio", # can use webui, ollama, llamacpp, etc. + "model_endpoint": "http://localhost:1234", # the IP address of your LLM backend + "context_window": 8192, # the context window of your model (for Mistral 7B-based models, it's likely 8192) + }, + ] # If USE_MEMGPT is False, then this example will be the same as the official AutoGen repo # (https://github.com/microsoft/autogen/blob/main/notebook/agentchat_groupchat.ipynb) # If USE_MEMGPT is True, then we swap out the "coder" agent with a MemGPT agent USE_MEMGPT = True -USE_AUTOGEN_WORKFLOW = True - # Set to True if you want to print MemGPT's inner workings. DEBUG = False @@ -101,26 +109,16 @@ else: # In our example, we swap this AutoGen agent with a MemGPT agent # This MemGPT agent will have all the benefits of MemGPT, ie persistent memory, etc. - if not USE_AUTOGEN_WORKFLOW: - coder = create_autogen_memgpt_agent( - "MemGPT_coder", - persona_description="I am a 10x engineer, trained in Python. I was the first engineer at Uber " - "(which I make sure to tell everyone I work with).", - user_description=f"You are participating in a group chat with a user ({user_proxy.name}) " - f"and a product manager ({pm.name}).", - model=config_list_memgpt[0]["model"], - interface_kwargs=interface_kwargs, - ) - else: - coder = create_memgpt_autogen_agent_from_config( - "MemGPT_coder", - llm_config=llm_config_memgpt, - system_message=f"I am a 10x engineer, trained in Python. I was the first engineer at Uber " - f"(which I make sure to tell everyone I work with).\n" - f"You are participating in a group chat with a user ({user_proxy.name}) " - f"and a product manager ({pm.name}).", - interface_kwargs=interface_kwargs, - ) + coder = create_memgpt_autogen_agent_from_config( + "MemGPT_coder", + llm_config=llm_config_memgpt, + system_message=f"I am a 10x engineer, trained in Python. I was the first engineer at Uber " + f"(which I make sure to tell everyone I work with).\n" + f"You are participating in a group chat with a user ({user_proxy.name}) " + f"and a product manager ({pm.name}).", + interface_kwargs=interface_kwargs, + default_auto_reply="...", # Set a default auto-reply message here (non-empty auto-reply is required for LM Studio) + ) # Initialize the group chat between the user and two LLM agents (PM and coder) groupchat = autogen.GroupChat(agents=[user_proxy, pm, coder], messages=[], max_round=12) @@ -129,5 +127,5 @@ # Begin the group chat with a message from the user user_proxy.initiate_chat( manager, - message="I want to design an app to make me one million dollars in one month. " "Yes, your heard that right.", + message="I want to design an app to make me one million dollars in one month. Yes, your heard that right.", ) diff --git a/memgpt/autogen/memgpt_agent.py b/memgpt/autogen/memgpt_agent.py index dee58e9cb1..19e8aeda2e 100644 --- a/memgpt/autogen/memgpt_agent.py +++ b/memgpt/autogen/memgpt_agent.py @@ -25,59 +25,70 @@ def create_memgpt_autogen_agent_from_config( function_map: Optional[Dict[str, Callable]] = None, code_execution_config: Optional[Union[Dict, bool]] = None, llm_config: Optional[Union[Dict, bool]] = None, + # config setup for non-memgpt agents: + nonmemgpt_llm_config: Optional[Union[Dict, bool]] = None, default_auto_reply: Optional[Union[str, Dict, None]] = "", interface_kwargs: Dict = None, ): - """Construct AutoGen config workflow in a clean way.""" + """Same function signature as used in base AutoGen, but creates a MemGPT agent + + Construct AutoGen config workflow in a clean way. + """ + llm_config = llm_config["config_list"][0] if interface_kwargs is None: interface_kwargs = {} - model = constants.DEFAULT_MEMGPT_MODEL if llm_config is None else llm_config["config_list"][0]["model"] + # The "system message" in AutoGen becomes the persona in MemGPT persona_desc = personas.DEFAULT if system_message == "" else system_message + # The user profile is based on the input mode if human_input_mode == "ALWAYS": - user_desc = humans.DEFAULT + user_desc = "" elif human_input_mode == "TERMINATE": user_desc = "Work by yourself, the user won't reply until you output `TERMINATE` to end the conversation." else: user_desc = "Work by yourself, the user won't reply. Elaborate as much as possible." + # Create an AgentConfig option from the inputs + agent_config = AgentConfig( + name=name, + persona=persona_desc, + human=user_desc, + preset=llm_config["preset"], + model=llm_config["model"], + model_wrapper=llm_config["model_wrapper"], + model_endpoint_type=llm_config["model_endpoint_type"], + model_endpoint=llm_config["model_endpoint"], + context_window=llm_config["context_window"], + ) + if function_map is not None or code_execution_config is not None: raise NotImplementedError autogen_memgpt_agent = create_autogen_memgpt_agent( - name, - preset=presets.DEFAULT_PRESET, - model=model, - persona_description=persona_desc, - user_description=user_desc, + agent_config, + default_auto_reply=default_auto_reply, is_termination_msg=is_termination_msg, interface_kwargs=interface_kwargs, ) if human_input_mode != "ALWAYS": coop_agent1 = create_autogen_memgpt_agent( - name, - preset=presets.DEFAULT_PRESET, - model=model, - persona_description=persona_desc, - user_description=user_desc, + agent_config, + default_auto_reply=default_auto_reply, is_termination_msg=is_termination_msg, interface_kwargs=interface_kwargs, ) if default_auto_reply != "": coop_agent2 = UserProxyAgent( - name, + "User_proxy", human_input_mode="NEVER", default_auto_reply=default_auto_reply, ) else: coop_agent2 = create_autogen_memgpt_agent( - name, - preset=presets.DEFAULT_PRESET, - model=model, - persona_description=persona_desc, - user_description=user_desc, + agent_config, + default_auto_reply=default_auto_reply, is_termination_msg=is_termination_msg, interface_kwargs=interface_kwargs, ) @@ -87,7 +98,8 @@ def create_memgpt_autogen_agent_from_config( messages=[], max_round=12 if max_consecutive_auto_reply is None else max_consecutive_auto_reply, ) - manager = GroupChatManager(name=name, groupchat=groupchat, llm_config=llm_config) + assert nonmemgpt_llm_config is not None + manager = GroupChatManager(name=name, groupchat=groupchat, llm_config=nonmemgpt_llm_config) return manager else: @@ -95,15 +107,13 @@ def create_memgpt_autogen_agent_from_config( def create_autogen_memgpt_agent( - autogen_name, - preset=presets.DEFAULT_PRESET, - model=constants.DEFAULT_MEMGPT_MODEL, - persona_description=personas.DEFAULT, - user_description=humans.DEFAULT, + agent_config, + # interface and persistence manager interface=None, interface_kwargs={}, persistence_manager=None, persistence_manager_kwargs=None, + default_auto_reply: Optional[Union[str, Dict, None]] = "", is_termination_msg: Optional[Callable[[Dict], bool]] = None, ): """ @@ -119,16 +129,9 @@ def create_autogen_memgpt_agent( } ``` """ - agent_config = AgentConfig( - # name=autogen_name, - # TODO: more gracefully integrate reuse of MemGPT agents. Right now, we are creating a new MemGPT agent for - # every call to this function, because those scripts using create_autogen_memgpt_agent may contain calls - # to non-idempotent agent functions like `attach`. - persona=persona_description, - human=user_description, - model=model, - preset=presets.DEFAULT_PRESET, - ) + # TODO: more gracefully integrate reuse of MemGPT agents. Right now, we are creating a new MemGPT agent for + # every call to this function, because those scripts using create_autogen_memgpt_agent may contain calls + # to non-idempotent agent functions like `attach`. interface = AutoGenInterface(**interface_kwargs) if interface is None else interface if persistence_manager_kwargs is None: @@ -138,18 +141,19 @@ def create_autogen_memgpt_agent( persistence_manager = LocalStateManager(**persistence_manager_kwargs) if persistence_manager is None else persistence_manager memgpt_agent = presets.use_preset( - preset, + agent_config.preset, agent_config, - model, - persona_description, - user_description, + agent_config.model, + agent_config.persona, # note: extracting the raw text, not pulling from a file + agent_config.human, # note: extracting raw text, not pulling from a file interface, persistence_manager, ) autogen_memgpt_agent = MemGPTAgent( - name=autogen_name, + name=agent_config.name, agent=memgpt_agent, + default_auto_reply=default_auto_reply, is_termination_msg=is_termination_msg, ) return autogen_memgpt_agent @@ -163,6 +167,7 @@ def __init__( skip_verify=False, concat_other_agent_messages=False, is_termination_msg: Optional[Callable[[Dict], bool]] = None, + default_auto_reply: Optional[Union[str, Dict, None]] = "", ): super().__init__(name) self.agent = agent @@ -170,6 +175,7 @@ def __init__( self.concat_other_agent_messages = concat_other_agent_messages self.register_reply([Agent, None], MemGPTAgent._generate_reply_for_user_message) self.messages_processed_up_to_idx = 0 + self._default_auto_reply = default_auto_reply self._is_termination_msg = is_termination_msg if is_termination_msg is not None else (lambda x: x == "TERMINATE") @@ -289,4 +295,9 @@ def pretty_concat(messages): for m in messages: lines.append(f"{m}") ret["content"] = "\n".join(lines) + + # prevent error in LM Studio caused by scenarios where MemGPT didn't say anything + if ret["content"] in ["", "\n"]: + ret["content"] = "..." + return ret diff --git a/memgpt/cli/cli.py b/memgpt/cli/cli.py index 64b33ef1b1..1723716580 100644 --- a/memgpt/cli/cli.py +++ b/memgpt/cli/cli.py @@ -223,6 +223,7 @@ def attach( typer.secho(f"Ingesting {size} passages into {agent_config.name}", fg=typer.colors.GREEN) page_size = 100 generator = source_storage.get_all_paginated(page_size=page_size) # yields List[Passage] + passages = [] for i in tqdm(range(0, size, page_size)): passages = next(generator) dest_storage.insert_many(passages)