From 1142fbc545e186589359ada7b4834634a31a784b Mon Sep 17 00:00:00 2001 From: Beibin Li Date: Tue, 25 Jul 2023 12:19:16 -0700 Subject: [PATCH 1/5] Add OptiGuide: agent and notebook --- flaml/autogen/agent/opti_guide.py | 340 ++++++++++++++ notebook/autogen_optiguide.ipynb | 747 ++++++++++++++++++++++++++++++ 2 files changed, 1087 insertions(+) create mode 100644 flaml/autogen/agent/opti_guide.py create mode 100644 notebook/autogen_optiguide.ipynb diff --git a/flaml/autogen/agent/opti_guide.py b/flaml/autogen/agent/opti_guide.py new file mode 100644 index 0000000000..c64146ec71 --- /dev/null +++ b/flaml/autogen/agent/opti_guide.py @@ -0,0 +1,340 @@ +"""A simplified implementation of OptiGuide framework with FLAML. +For more details, read: https://arxiv.org/abs/2307.03875 + +The design here is that a user asked a question, then OptiGuide will answer +it. + +The OptiGuide agent will interact with the LLMProxy agent to access LLMs. + +Notes: +1. We assume there is a Gurobi model `m` in the global scope. +2. Some helper functions can be integrated with other FLAML components, such as +code_utils.py. Please note and update the code accordingly. +3. We do not handle docker here. Please test and add docker support if missing. +4. We simplify the evaluation only to "DATA CODE" and "CONSTRAINT CODE", where +we would insert the newly added code. +""" +import pdb +import re +from typing import Dict, Union + +import gurobipy as gp +from eventlet.timeout import Timeout +from flaml import oai +from flaml.autogen.code_utils import DEFAULT_MODEL, extract_code +from gurobipy import GRB +from termcolor import colored + +from .agent import Agent +from .assistant_agent import AssistantAgent +from .user_proxy_agent import UserProxyAgent + +ASSIST_SYSTEM_MSG = """You are OptiGuide, +an agent to write Python code and to answer users questions for supply chain-related coding project. + + +You are a chatbot to explain solutions from a Gurobi/Python solver. + + +--- SOURCE CODE --- +{source_code} + +--- DOC STR --- +{doc_str} + + +Here are some example questions and their answers and codes: +--- EXAMPLES --- +{example_qa} + + +--- + + +The execution result of the original source code is below. +--- Original Result --- +{execution_result} + + +Note that your written code will be added to the lines with substring "# OPTIGUIDE *** CODE GOES HERE". +So, you don't need to write other code, such as m.optimize() or m.update(). +You just need to write code snippet. +""" + +SAFEGUARD_SYSTEM_MSG = """ +Given the original source code: +{source_code} + +Is the following code safe (not malicious code to break security and privacy) to run? +Answer only one word. +If not safe, answer `DANGER`; else, answer `SAFE`. +""" + + +DATA_CODE_STR = "# OPTIGUIDE DATA CODE GOES HERE" +CONSTRAINT_CODE_STR = "# OPTIGUIDE CONSTRAINT CODE GOES HERE" + + +# %% +class OptiGuideAgent(Agent): + """OptiGuide is an agent to write Python code and to answer users + questions for supply chain-related coding project. + + Here, the OptiGuide agent manages three agents (coder, safeguard, and interpreter) + and two assistant agents (pencil and shield). + """ + + def __init__(self, name, source_code, doc_str="", example_qa="", **config): + """ + Args: + name (str): agent name. + source_code (str): The original source code to run. + system_message (str): system message to be sent to the agent. + **config (dict): other configurations allowed in + [oai.Completion.create](../oai/Completion#create). + These configurations will be used when invoking LLM. + """ + assert source_code.find(DATA_CODE_STR) >= 0, "DATA_CODE_STR not found." + assert source_code.find(CONSTRAINT_CODE_STR) >= 0, "CONSTRAINT_CODE_STR not found." + + super().__init__(name, system_message="") + self._source_code = source_code + self._sender_dict = {} + + execution_result = _run_with_exec(source_code) + self.writing_sys_msg = ASSIST_SYSTEM_MSG.format( + source_code=source_code, doc_str=doc_str, example_qa=example_qa, execution_result=execution_result + ) + self.shield_sys_msg = SAFEGUARD_SYSTEM_MSG.format(source_code=source_code) + + self._debug_opportunity = 3 + + def receive(self, message: Union[Dict, str], sender): + """Receive a message from the sender agent. + Once a message is received, this function sends a reply to the sender or simply stop. + The reply can be generated automatically or entered manually by a human. + + OptiGuide will invoke its components: coder, safeguard, and interpret + to generate the reply. + + The steps follows the one in Figure 2 of the OptiGuide paper. + """ + # Step 1: receive the message + message = self._message_to_dict(message) + super().receive(message, sender) + # default reply is empty (i.e., no reply, in this case we will try to generate auto reply) + reply = "" + question = message["content"] + + user_chat_history = f"\nHere are the history of discussions:\n{self._oai_conversations[sender.name]}" + + # Spawn coder, interpreter, and safeguard + # Note: we use the same agent for coding and interpreting, so that they + # can share the same chat history. + coder = interpreter = UserProxyAgent( + "coder interpreter", human_input_mode="NEVER", max_consecutive_auto_reply=0, code_execution_config=False + ) + safeguard = UserProxyAgent( + "safeguard", human_input_mode="NEVER", max_consecutive_auto_reply=0, code_execution_config=False + ) + + # Spawn the assistants for coder, interpreter, and safeguard + pencil = AssistantAgent("pencil", system_message=self.writing_sys_msg + user_chat_history) + shield = AssistantAgent("shield", system_message=self.shield_sys_msg + user_chat_history) + + debug_opportunity = self._debug_opportunity + + new_code = None + execution_rst = None + success = False + while execution_rst is None and debug_opportunity > 0: + # Step 2: Write code + if new_code is None: + # The first time we are trying to solve the question in this session + msg = CODE_PROMPT.format(question=question) + else: + # debug code + msg = DEBUG_PROMPT.format(question, new_code, execution_rst) + + coder.send(message=msg, recipient=pencil) + new_code = coder.oai_conversations[pencil.name][-1]["content"] + new_code = extract_code(new_code)[0][1] # First code block, the code (excluding language) + print(colored(new_code, "green")) + + # Step 3: safeguard + safeguard.send(message=SAFEGUARD_PROMPT.format(code=new_code), recipient=shield) + safe_msg = safeguard.oai_conversations[shield.name][-1]["content"] + print("Safety:", colored(safe_msg, "blue")) + + if safe_msg.find("DANGER") < 0: + # Step 4 and 5: Run the code and obtain the results + src_code = _insert_code(self._source_code, new_code) + execution_rst = _run_with_exec(src_code) + + print(colored(str(execution_rst), "yellow")) + if type(execution_rst) in [str, int, float]: + success = True + break # we successfully run the code and get the result + else: + # DANGER: If not safe, try to debug. Redo coding + execution_rst = """ + Sorry, this new code is not safe to run. I would not allow you to execute it. + Please try to find a new way (coding) to answer the question.""" + + # Try to debug and write code again + debug_opportunity -= 1 + + if success: + # Step 6 - 7: interpret results + interpret_msg = f"""To answer the question: {question} + + I wrote the code: {new_code} + + Here are the execution results: {execution_rst} + + Can you try to organize these information to a human readable answer? + Remember to compare the new results to the original results you obtained in the beginning. + + --- HUMAN READABLE ANSWER --- + """ + interpreter.send(message=interpret_msg, recipient=pencil) + reply = interpreter.oai_conversations[pencil.name][-1]["content"] + else: + reply = "Sorry. I cannot answer your question." + + # Finally, step 8: send reply + self.send(reply, sender) + + +# %% Helper functions to edit and run code. +# Here, we use a simplified approach to run the code snippet, which would +# replace substrings in the source code to get an updated version of code. +# Then, we use exec to run the code snippet. +# This approach replicate the evaluation section of the OptiGuide paper. + + +def _run_with_exec(src_code: str) -> object: + """Run the code snippet with exec. + + Args: + src_code (str): The source code to run. + + Returns: + object: The result of the code snippet. + If the code succeed, returns the objective value (float or string). + else, return the error (exception) + """ + locals_dict = {} + locals_dict.update(locals()) + locals_dict.update(globals()) + + timeout = Timeout(60, Exception("This is a timeout exception, in case GPT's code falls into infinite loop.")) + try: + exec(src_code, locals_dict, locals_dict) + except Exception as e: + return e + finally: + timeout.cancel() + + try: + status = locals_dict["m"].Status + if status != GRB.OPTIMAL: + if status == GRB.UNBOUNDED: + ans = "unbounded" + elif status == GRB.INF_OR_UNBD: + ans = "inf_or_unbound" + elif status == GRB.INFEASIBLE: + ans = "infeasible" + else: + ans = "Model Status: " + str(status) + else: + ans = "Optimization problem solved. The objective value is: " + str(locals_dict["m"].objVal) + except Exception as e: + return e + + return ans + + +def _replace(src_code: str, old_code: str, new_code: str) -> str: + """ + Inserts new code into the source code by replacing a specified old code block. + + Args: + src_code (str): The source code to modify. + old_code (str): The code block to be replaced. + new_code (str): The new code block to insert. + + Returns: + str: The modified source code with the new code inserted. + + Raises: + None + + Example: + src_code = 'def hello_world():\n print("Hello, world!")\n\n# Some other code here' + old_code = 'print("Hello, world!")' + new_code = 'print("Bonjour, monde!")\nprint("Hola, mundo!")' + modified_code = insert(src_code, old_code, new_code) + print(modified_code) + # Output: + # def hello_world(): + # print("Bonjour, monde!") + # print("Hola, mundo!") + # Some other code here + """ + pattern = r"( *){old_code}".format(old_code=old_code) + head_spaces = re.search(pattern, src_code, flags=re.DOTALL).group(1) + new_code = "\n".join([head_spaces + line for line in new_code.split("\n")]) + rst = re.sub(pattern, new_code, src_code) + return rst + + +def _insert_code(src_code: str, new_lines: str) -> str: + """insert a code patch into the source code. + + + Args: + src_code (str): the full source code + new_lines (str): The new code. + + Returns: + str: the full source code after insertion (replacement). + """ + if new_lines.find("addConstr") >= 0: + return _replace(src_code, CONSTRAINT_CODE_STR, new_lines) + else: + return _replace(src_code, DATA_CODE_STR, new_lines) + + +# %% Prompt for OptiGuide +CODE_PROMPT = """ +Question: {question} +Answer Code: +""" + +DEBUG_PROMPT = """ +To answer a question, the code snippet is added. +--- QUESTION --- +{question} + + +Pay attention to the added code below +--- Added Code --- +{code_snippet} + + +While running Python code, you encountered the error: +--- ERROR --- +{error_message} + +Please try to resolve this bug, and rewrite the code snippet. +--- NEW CODE --- +""" + + +SAFEGUARD_PROMPT = """ +--- Code --- +{code} + +--- One-Word Answer: SAFE or DANGER --- +""" diff --git a/notebook/autogen_optiguide.ipynb b/notebook/autogen_optiguide.ipynb new file mode 100644 index 0000000000..d70a245223 --- /dev/null +++ b/notebook/autogen_optiguide.ipynb @@ -0,0 +1,747 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a461c72d", + "metadata": {}, + "source": [ + "# OptiGuide Example\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "id": "59a05fc7", + "metadata": {}, + "source": [ + "Here we give a \"zero-shot\" learning example." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "1810e663", + "metadata": {}, + "outputs": [], + "source": [ + "# %%capture # no printing logs here.\n", + "# # Install Gurobi for optimization\n", + "# %pip install gurobipy\n", + "# %pip install eventlet\n", + "\n", + "# # Install FLAML for agents\n", + "# %pip install \"flaml[optiguide]\"" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "9a3b79c4", + "metadata": {}, + "outputs": [], + "source": [ + "# test Gurobi installation\n", + "import gurobipy as gp\n", + "from gurobipy import GRB\n", + "from eventlet.timeout import Timeout\n", + "\n", + "import re\n", + "import requests # for loading the example source code\n" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "aedf19e7", + "metadata": {}, + "outputs": [], + "source": [ + "from flaml.autogen.agent import Agent\n", + "from flaml.autogen.agent.opti_guide import OptiGuideAgent\n", + "\n", + "\n", + "from flaml import oai\n", + "config_list = oai.config_list_gpt4_gpt35()\n", + "oai.ChatCompletion.start_logging()\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "3ee5eea9", + "metadata": {}, + "outputs": [], + "source": [ + "import openai\n", + "import os\n", + "\n", + "openai.api_type = \"azure\"\n", + "openai.api_base = \"https://msrcore.openai.azure.com\"\n", + "openai.api_version = \"2023-03-15-preview\"\n", + "openai.api_key = os.getenv(\"CORE_AZURE_KEY\").strip().rstrip()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ca962ac5", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "e2d8dce8", + "metadata": { + "code_folding": [] + }, + "outputs": [], + "source": [ + "# Load the Coffee example\n", + "code = \"\"\"\n", + "\n", + "import time\n", + "\n", + "from gurobipy import GRB, Model\n", + "\n", + "# Example data\n", + "\n", + "capacity_in_supplier = {'supplier1': 150, 'supplier2': 50, 'supplier3': 100}\n", + "\n", + "shipping_cost_from_supplier_to_roastery = {\n", + " ('supplier1', 'roastery1'): 5,\n", + " ('supplier1', 'roastery2'): 4,\n", + " ('supplier2', 'roastery1'): 6,\n", + " ('supplier2', 'roastery2'): 3,\n", + " ('supplier3', 'roastery1'): 2,\n", + " ('supplier3', 'roastery2'): 7\n", + "}\n", + "\n", + "roasting_cost_light = {'roastery1': 3, 'roastery2': 5}\n", + "\n", + "roasting_cost_dark = {'roastery1': 5, 'roastery2': 6}\n", + "\n", + "shipping_cost_from_roastery_to_cafe = {\n", + " ('roastery1', 'cafe1'): 5,\n", + " ('roastery1', 'cafe2'): 3,\n", + " ('roastery1', 'cafe3'): 6,\n", + " ('roastery2', 'cafe1'): 4,\n", + " ('roastery2', 'cafe2'): 5,\n", + " ('roastery2', 'cafe3'): 2\n", + "}\n", + "\n", + "light_coffee_needed_for_cafe = {'cafe1': 20, 'cafe2': 30, 'cafe3': 40}\n", + "\n", + "dark_coffee_needed_for_cafe = {'cafe1': 20, 'cafe2': 20, 'cafe3': 100}\n", + "\n", + "cafes = list(set(i[1] for i in shipping_cost_from_roastery_to_cafe.keys()))\n", + "roasteries = list(\n", + " set(i[1] for i in shipping_cost_from_supplier_to_roastery.keys()))\n", + "suppliers = list(\n", + " set(i[0] for i in shipping_cost_from_supplier_to_roastery.keys()))\n", + "\n", + "\n", + "# Create a new model\n", + "model = Model(\"coffee_distribution\")\n", + "m = model\n", + "\n", + "# OPTIGUIDE DATA CODE GOES HERE\n", + "\n", + "\n", + "# Create variables\n", + "x = model.addVars(shipping_cost_from_supplier_to_roastery.keys(),\n", + " vtype=GRB.INTEGER,\n", + " name=\"x\")\n", + "y_light = model.addVars(shipping_cost_from_roastery_to_cafe.keys(),\n", + " vtype=GRB.INTEGER,\n", + " name=\"y_light\")\n", + "y_dark = model.addVars(shipping_cost_from_roastery_to_cafe.keys(),\n", + " vtype=GRB.INTEGER,\n", + " name=\"y_dark\")\n", + "\n", + "# Set objective\n", + "model.setObjective(\n", + " sum(x[i] * shipping_cost_from_supplier_to_roastery[i]\n", + " for i in shipping_cost_from_supplier_to_roastery.keys()) +\n", + " sum(roasting_cost_light[r] * y_light[r, c] +\n", + " roasting_cost_dark[r] * y_dark[r, c]\n", + " for r, c in shipping_cost_from_roastery_to_cafe.keys()) + sum(\n", + " (y_light[j] + y_dark[j]) * shipping_cost_from_roastery_to_cafe[j]\n", + " for j in shipping_cost_from_roastery_to_cafe.keys()), GRB.MINIMIZE)\n", + "\n", + "# Conservation of flow constraint\n", + "for r in set(i[1] for i in shipping_cost_from_supplier_to_roastery.keys()):\n", + " model.addConstr(\n", + " sum(x[i]\n", + " for i in shipping_cost_from_supplier_to_roastery.keys()\n", + " if i[1] == r) == sum(\n", + " y_light[j] + y_dark[j]\n", + " for j in shipping_cost_from_roastery_to_cafe.keys()\n", + " if j[0] == r), f\"flow_{r}\")\n", + "\n", + "# Add supply constraints\n", + "for s in set(i[0] for i in shipping_cost_from_supplier_to_roastery.keys()):\n", + " model.addConstr(\n", + " sum(x[i]\n", + " for i in shipping_cost_from_supplier_to_roastery.keys()\n", + " if i[0] == s) <= capacity_in_supplier[s], f\"supply_{s}\")\n", + "\n", + "# Add demand constraints\n", + "for c in set(i[1] for i in shipping_cost_from_roastery_to_cafe.keys()):\n", + " model.addConstr(\n", + " sum(y_light[j]\n", + " for j in shipping_cost_from_roastery_to_cafe.keys()\n", + " if j[1] == c) >= light_coffee_needed_for_cafe[c],\n", + " f\"light_demand_{c}\")\n", + " model.addConstr(\n", + " sum(y_dark[j]\n", + " for j in shipping_cost_from_roastery_to_cafe.keys()\n", + " if j[1] == c) >= dark_coffee_needed_for_cafe[c], f\"dark_demand_{c}\")\n", + "\n", + "# Optimize model\n", + "model.optimize()\n", + "\n", + "# OPTIGUIDE CONSTRAINT CODE GOES HERE\n", + "\n", + "# Solve\n", + "m.update()\n", + "model.optimize()\n", + "\n", + "print(time.ctime())\n", + "if m.status == GRB.OPTIMAL:\n", + " print(f'Optimal cost: {m.objVal}')\n", + "else:\n", + " print(\"Not solved to optimality. Optimization status:\", m.status)\n", + "\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "e31c4b36", + "metadata": { + "code_folding": [] + }, + "outputs": [], + "source": [ + "example_qa = \"\"\"\n", + "----------\n", + "Question: Why is it not recommended to use just one supplier for roastery 2?\n", + "Answer Code:\n", + "```python\n", + "z = m.addVars(suppliers, vtype=GRB.BINARY, name=\"z\")\n", + "m.addConstr(sum(z[s] for s in suppliers) <= 1, \"_\")\n", + "for s in suppliers:\n", + " m.addConstr(x[s,'roastery2'] <= capacity_in_supplier[s] * z[s], \"_\")\n", + "```\n", + "\n", + "----------\n", + "Question: What if there's a 13% jump in the demand for light coffee at cafe1?\n", + "Answer Code:\n", + "```python\n", + "light_coffee_needed_for_cafe[\"cafe1\"] = light_coffee_needed_for_cafe[\"cafe1\"] * (1 + 13/100)\n", + "```\n", + "\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8d1dfd35", + "metadata": { + "code_folding": [] + }, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "541f1459", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "af53727c", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Set parameter TokenServer to value \"ishai-z420\"\n", + "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)\n", + "Thread count: 64 physical cores, 128 logical processors, using up to 32 threads\n", + "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", + "Model fingerprint: 0xd4254685\n", + "Variable types: 0 continuous, 18 integer (0 binary)\n", + "Coefficient statistics:\n", + " Matrix range [1e+00, 1e+00]\n", + " Objective range [2e+00, 1e+01]\n", + " Bounds range [0e+00, 0e+00]\n", + " RHS range [2e+01, 2e+02]\n", + "Found heuristic solution: objective 3490.0000000\n", + "Presolve time: 0.00s\n", + "Presolved: 11 rows, 18 columns, 36 nonzeros\n", + "Variable types: 0 continuous, 18 integer (0 binary)\n", + "Found heuristic solution: objective 3486.0000000\n", + "\n", + "Root relaxation: objective 2.470000e+03, 11 iterations, 0.00 seconds (0.00 work units)\n", + "\n", + " Nodes | Current Node | Objective Bounds | Work\n", + " Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n", + "\n", + "* 0 0 0 2470.0000000 2470.00000 0.00% - 0s\n", + "\n", + "Explored 1 nodes (11 simplex iterations) in 0.02 seconds (0.00 work units)\n", + "Thread count was 32 (of 128 available processors)\n", + "\n", + "Solution count 3: 2470 3486 3490 \n", + "\n", + "Optimal solution found (tolerance 1.00e-04)\n", + "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", + "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)\n", + "Thread count: 64 physical cores, 128 logical processors, using up to 32 threads\n", + "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", + "Model fingerprint: 0xd4254685\n", + "Variable types: 0 continuous, 18 integer (0 binary)\n", + "Coefficient statistics:\n", + " Matrix range [1e+00, 1e+00]\n", + " Objective range [2e+00, 1e+01]\n", + " Bounds range [0e+00, 0e+00]\n", + " RHS range [2e+01, 2e+02]\n", + "Presolved: 11 rows, 18 columns, 36 nonzeros\n", + "\n", + "Continuing optimization...\n", + "\n", + "\n", + "Explored 1 nodes (11 simplex iterations) in 0.01 seconds (0.00 work units)\n", + "Thread count was 32 (of 128 available processors)\n", + "\n", + "Solution count 3: 2470 3486 3490 \n", + "\n", + "Optimal solution found (tolerance 1.00e-04)\n", + "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", + "Tue Jul 25 12:18:45 2023\n", + "Optimal cost: 2470.0\n" + ] + } + ], + "source": [ + "agent = OptiGuideAgent(name=\"OptiGuide Coffee Example\", \n", + " source_code=code,\n", + "# doc_str=\"\", \n", + " example_qa=\"\")\n", + "\n", + "user = Agent(\"user\", system_message=\"\")\n", + "# user.organize_memory(agent)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7c3ca9f4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "24a76f67", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "user (to OptiGuide Coffee Example):\n", + "\n", + "What if we prohibit shipping from supplier 1 to roastery 2?\n", + "\n", + "--------------------------------------------------------------------------------\n", + "coder interpreter (to pencil):\n", + "\n", + "\n", + "Question: What if we prohibit shipping from supplier 1 to roastery 2?\n", + "Answer Code:\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "pencil (to coder interpreter):\n", + "\n", + "# Prohibit shipping from supplier 1 to roastery 2\n", + "model.addConstr(x[('supplier1', 'roastery2')] == 0, \"No_Shipping_S1R2\")\n", + "\n", + "# Solve after the new constraint\n", + "model.update()\n", + "model.optimize()\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[32m# Prohibit shipping from supplier 1 to roastery 2\n", + "model.addConstr(x[('supplier1', 'roastery2')] == 0, \"No_Shipping_S1R2\")\n", + "\n", + "# Solve after the new constraint\n", + "model.update()\n", + "model.optimize()\u001b[0m\n", + "safeguard (to shield):\n", + "\n", + "\n", + "--- Code ---\n", + "# Prohibit shipping from supplier 1 to roastery 2\n", + "model.addConstr(x[('supplier1', 'roastery2')] == 0, \"No_Shipping_S1R2\")\n", + "\n", + "# Solve after the new constraint\n", + "model.update()\n", + "model.optimize()\n", + "\n", + "--- One-Word Answer: SAFE or DANGER ---\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "shield (to safeguard):\n", + "\n", + "SAFE\n", + "\n", + "--------------------------------------------------------------------------------\n", + "Safety: \u001b[34mSAFE\u001b[0m\n", + "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)\n", + "Thread count: 64 physical cores, 128 logical processors, using up to 32 threads\n", + "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", + "Model fingerprint: 0xd4254685\n", + "Variable types: 0 continuous, 18 integer (0 binary)\n", + "Coefficient statistics:\n", + " Matrix range [1e+00, 1e+00]\n", + " Objective range [2e+00, 1e+01]\n", + " Bounds range [0e+00, 0e+00]\n", + " RHS range [2e+01, 2e+02]\n", + "Found heuristic solution: objective 3490.0000000\n", + "Presolve time: 0.00s\n", + "Presolved: 11 rows, 18 columns, 36 nonzeros\n", + "Variable types: 0 continuous, 18 integer (0 binary)\n", + "Found heuristic solution: objective 3486.0000000\n", + "\n", + "Root relaxation: objective 2.470000e+03, 11 iterations, 0.00 seconds (0.00 work units)\n", + "\n", + " Nodes | Current Node | Objective Bounds | Work\n", + " Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n", + "\n", + "* 0 0 0 2470.0000000 2470.00000 0.00% - 0s\n", + "\n", + "Explored 1 nodes (11 simplex iterations) in 0.01 seconds (0.00 work units)\n", + "Thread count was 32 (of 128 available processors)\n", + "\n", + "Solution count 3: 2470 3486 3490 \n", + "\n", + "Optimal solution found (tolerance 1.00e-04)\n", + "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", + "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)\n", + "Thread count: 64 physical cores, 128 logical processors, using up to 32 threads\n", + "Optimize a model with 12 rows, 18 columns and 37 nonzeros\n", + "Model fingerprint: 0xe6485c72\n", + "Variable types: 0 continuous, 18 integer (0 binary)\n", + "Coefficient statistics:\n", + " Matrix range [1e+00, 1e+00]\n", + " Objective range [2e+00, 1e+01]\n", + " Bounds range [0e+00, 0e+00]\n", + " RHS range [2e+01, 2e+02]\n", + "\n", + "MIP start from previous solve did not produce a new incumbent solution\n", + "MIP start from previous solve violates constraint No_Shipping_S1R2 by 80.000000000\n", + "\n", + "Found heuristic solution: objective 3490.0000000\n", + "Presolve removed 2 rows and 1 columns\n", + "Presolve time: 0.00s\n", + "Presolved: 10 rows, 17 columns, 33 nonzeros\n", + "Variable types: 0 continuous, 17 integer (0 binary)\n", + "Found heuristic solution: objective 3485.0000000\n", + "\n", + "Root relaxation: objective 2.760000e+03, 10 iterations, 0.00 seconds (0.00 work units)\n", + "\n", + " Nodes | Current Node | Objective Bounds | Work\n", + " Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n", + "\n", + "* 0 0 0 2760.0000000 2760.00000 0.00% - 0s\n", + "\n", + "Explored 1 nodes (10 simplex iterations) in 0.01 seconds (0.00 work units)\n", + "Thread count was 32 (of 128 available processors)\n", + "\n", + "Solution count 3: 2760 3485 3490 \n", + "\n", + "Optimal solution found (tolerance 1.00e-04)\n", + "Best objective 2.760000000000e+03, best bound 2.760000000000e+03, gap 0.0000%\n", + "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)\n", + "Thread count: 64 physical cores, 128 logical processors, using up to 32 threads\n", + "Optimize a model with 12 rows, 18 columns and 37 nonzeros\n", + "Model fingerprint: 0xe6485c72\n", + "Variable types: 0 continuous, 18 integer (0 binary)\n", + "Coefficient statistics:\n", + " Matrix range [1e+00, 1e+00]\n", + " Objective range [2e+00, 1e+01]\n", + " Bounds range [0e+00, 0e+00]\n", + " RHS range [2e+01, 2e+02]\n", + "Presolved: 10 rows, 17 columns, 33 nonzeros\n", + "\n", + "Continuing optimization...\n", + "\n", + "\n", + "Explored 1 nodes (10 simplex iterations) in 0.00 seconds (0.00 work units)\n", + "Thread count was 32 (of 128 available processors)\n", + "\n", + "Solution count 3: 2760 3485 3490 \n", + "\n", + "Optimal solution found (tolerance 1.00e-04)\n", + "Best objective 2.760000000000e+03, best bound 2.760000000000e+03, gap 0.0000%\n", + "Tue Jul 25 12:18:45 2023\n", + "Optimal cost: 2760.0\n", + "\u001b[33mOptimization problem solved. The objective value is: 2760.0\u001b[0m\n", + "coder interpreter (to pencil):\n", + "\n", + "To answer the question: What if we prohibit shipping from supplier 1 to roastery 2?\n", + "\n", + " I wrote the code: # Prohibit shipping from supplier 1 to roastery 2\n", + "model.addConstr(x[('supplier1', 'roastery2')] == 0, \"No_Shipping_S1R2\")\n", + "\n", + "# Solve after the new constraint\n", + "model.update()\n", + "model.optimize()\n", + "\n", + " Here are the execution results: Optimization problem solved. The objective value is: 2760.0\n", + "\n", + " Can you try to organize these information to a human readable answer?\n", + " Remember to compare the new results to the original results you obtained in the beginning.\n", + "\n", + " --- HUMAN READABLE ANSWER ---\n", + " \n", + "\n", + "--------------------------------------------------------------------------------\n", + "pencil (to coder interpreter):\n", + "\n", + "After prohibiting the shipping from supplier 1 to roastery 2 and re-optimizing the model, the new objective value is 2760.0. This is an increase of 290.0 compared to the original objective value of 2470.0. This means that the total cost to fulfill the supply chain requirements has increased due to the additional constraint we imposed.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "OptiGuide Coffee Example (to user):\n", + "\n", + "After prohibiting the shipping from supplier 1 to roastery 2 and re-optimizing the model, the new objective value is 2760.0. This is an increase of 290.0 compared to the original objective value of 2470.0. This means that the total cost to fulfill the supply chain requirements has increased due to the additional constraint we imposed.\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + } + ], + "source": [ + "user.send(\"What if we prohibit shipping from supplier 1 to roastery 2?\", agent)\n" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "cbdd1f28", + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "user (to OptiGuide Coffee Example):\n", + "\n", + "What is the impact of supplier1 being able to supply only half the quantity at present?\n", + "\n", + "--------------------------------------------------------------------------------\n", + "coder interpreter (to pencil):\n", + "\n", + "\n", + "Question: What is the impact of supplier1 being able to supply only half the quantity at present?\n", + "Answer Code:\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "pencil (to coder interpreter):\n", + "\n", + "To examine the impact of supplier1 being able to supply only half the quantity, we can update the capacity in the `capacity_in_supplier` dictionary for supplier1 and reoptimize the model.\n", + "\n", + "Here's the code snippet to update the capacity of supplier1:\n", + "\n", + "```python\n", + "capacity_in_supplier['supplier1'] = capacity_in_supplier['supplier1'] // 2\n", + "model.update()\n", + "\n", + "model.optimize()\n", + "```\n", + "\n", + "Add this code snippet to the lines with substring \"# OPTIGUIDE *** CODE GOES HERE\" and check the optimization result to see the impact on the objective value.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[32mcapacity_in_supplier['supplier1'] = capacity_in_supplier['supplier1'] // 2\n", + "model.update()\n", + "\n", + "model.optimize()\u001b[0m\n", + "safeguard (to shield):\n", + "\n", + "\n", + "--- Code ---\n", + "capacity_in_supplier['supplier1'] = capacity_in_supplier['supplier1'] // 2\n", + "model.update()\n", + "\n", + "model.optimize()\n", + "\n", + "--- One-Word Answer: SAFE or DANGER ---\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "shield (to safeguard):\n", + "\n", + "SAFE\n", + "\n", + "--------------------------------------------------------------------------------\n", + "Safety: \u001b[34mSAFE\u001b[0m\n", + "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)\n", + "Thread count: 64 physical cores, 128 logical processors, using up to 32 threads\n", + "Optimize a model with 0 rows, 0 columns and 0 nonzeros\n", + "Model fingerprint: 0xf9715da1\n", + "Coefficient statistics:\n", + " Matrix range [0e+00, 0e+00]\n", + " Objective range [0e+00, 0e+00]\n", + " Bounds range [0e+00, 0e+00]\n", + " RHS range [0e+00, 0e+00]\n", + "Presolve time: 0.00s\n", + "Presolve: All rows and columns removed\n", + "Iteration Objective Primal Inf. Dual Inf. Time\n", + " 0 0.0000000e+00 0.000000e+00 0.000000e+00 0s\n", + "\n", + "Solved in 0 iterations and 0.01 seconds (0.00 work units)\n", + "Optimal objective 0.000000000e+00\n", + "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)\n", + "Thread count: 64 physical cores, 128 logical processors, using up to 32 threads\n", + "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", + "Model fingerprint: 0x081d397e\n", + "Variable types: 0 continuous, 18 integer (0 binary)\n", + "Coefficient statistics:\n", + " Matrix range [1e+00, 1e+00]\n", + " Objective range [2e+00, 1e+01]\n", + " Bounds range [0e+00, 0e+00]\n", + " RHS range [2e+01, 1e+02]\n", + "Presolve time: 0.00s\n", + "Presolved: 11 rows, 18 columns, 36 nonzeros\n", + "Variable types: 0 continuous, 18 integer (0 binary)\n", + "\n", + "Root relaxation: infeasible, 14 iterations, 0.00 seconds (0.00 work units)\n", + "\n", + " Nodes | Current Node | Objective Bounds | Work\n", + " Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n", + "\n", + " 0 0 infeasible 0 - infeasible - - 0s\n", + "\n", + "Explored 1 nodes (14 simplex iterations) in 0.01 seconds (0.00 work units)\n", + "Thread count was 32 (of 128 available processors)\n", + "\n", + "Solution count 0\n", + "\n", + "Model is infeasible\n", + "Best objective -, best bound -, gap -\n", + "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)\n", + "Thread count: 64 physical cores, 128 logical processors, using up to 32 threads\n", + "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", + "Model fingerprint: 0x081d397e\n", + "Variable types: 0 continuous, 18 integer (0 binary)\n", + "Coefficient statistics:\n", + " Matrix range [1e+00, 1e+00]\n", + " Objective range [2e+00, 1e+01]\n", + " Bounds range [0e+00, 0e+00]\n", + " RHS range [2e+01, 1e+02]\n", + "Presolved: 11 rows, 18 columns, 36 nonzeros\n", + "\n", + "Continuing optimization...\n", + "\n", + "\n", + "Explored 1 nodes (14 simplex iterations) in 0.01 seconds (0.00 work units)\n", + "Thread count was 32 (of 128 available processors)\n", + "\n", + "Solution count 0\n", + "\n", + "Model is infeasible\n", + "Best objective -, best bound -, gap -\n", + "Tue Jul 25 12:18:45 2023\n", + "Not solved to optimality. Optimization status: 3\n", + "\u001b[33minfeasible\u001b[0m\n", + "coder interpreter (to pencil):\n", + "\n", + "To answer the question: What is the impact of supplier1 being able to supply only half the quantity at present?\n", + "\n", + " I wrote the code: capacity_in_supplier['supplier1'] = capacity_in_supplier['supplier1'] // 2\n", + "model.update()\n", + "\n", + "model.optimize()\n", + "\n", + " Here are the execution results: infeasible\n", + "\n", + " Can you try to organize these information to a human readable answer?\n", + " Remember to compare the new results to the original results you obtained in the beginning.\n", + "\n", + " --- HUMAN READABLE ANSWER ---\n", + " \n", + "\n", + "--------------------------------------------------------------------------------\n", + "pencil (to coder interpreter):\n", + "\n", + "After updating the capacity of supplier1 to supply only half the quantity and re-optimizing the model, the optimization status indicates that the new problem is infeasible. This means that it is not possible to satisfy the supply chain requirements under the given constraints when supplier1 can supply only half of its original capacity. The original optimal objective value was 2470.0; however, due to the capacity constraint, the model cannot find a feasible solution anymore.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "OptiGuide Coffee Example (to user):\n", + "\n", + "After updating the capacity of supplier1 to supply only half the quantity and re-optimizing the model, the optimization status indicates that the new problem is infeasible. This means that it is not possible to satisfy the supply chain requirements under the given constraints when supplier1 can supply only half of its original capacity. The original optimal objective value was 2470.0; however, due to the capacity constraint, the model cannot find a feasible solution anymore.\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + } + ], + "source": [ + "user.send(\"What is the impact of supplier1 being able to supply only half the quantity at present?\", agent)\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.9.16" + }, + "toc": { + "base_numbering": 1, + "nav_menu": {}, + "number_sections": true, + "sideBar": true, + "skip_h1_title": false, + "title_cell": "Table of Contents", + "title_sidebar": "Contents", + "toc_cell": false, + "toc_position": {}, + "toc_section_display": true, + "toc_window_display": false + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From ea0e391bce5b0d16a039e8a92d4e241dca8ed33e Mon Sep 17 00:00:00 2001 From: Beibin Li Date: Tue, 25 Jul 2023 15:28:23 -0700 Subject: [PATCH 2/5] Optiguide notebook: add figures and URL 1. figures and code points to remote URL 2. simplify the prompt for the interpreter, because all information is already in the chat history. --- flaml/autogen/agent/opti_guide.py | 33 ++- notebook/autogen_optiguide.ipynb | 467 ++++++++++++++---------------- 2 files changed, 237 insertions(+), 263 deletions(-) diff --git a/flaml/autogen/agent/opti_guide.py b/flaml/autogen/agent/opti_guide.py index c64146ec71..f794840bd4 100644 --- a/flaml/autogen/agent/opti_guide.py +++ b/flaml/autogen/agent/opti_guide.py @@ -29,6 +29,8 @@ from .assistant_agent import AssistantAgent from .user_proxy_agent import UserProxyAgent + +# %% System Messages ASSIST_SYSTEM_MSG = """You are OptiGuide, an agent to write Python code and to answer users questions for supply chain-related coding project. @@ -55,7 +57,6 @@ --- Original Result --- {execution_result} - Note that your written code will be added to the lines with substring "# OPTIGUIDE *** CODE GOES HERE". So, you don't need to write other code, such as m.optimize() or m.update(). You just need to write code snippet. @@ -70,15 +71,15 @@ If not safe, answer `DANGER`; else, answer `SAFE`. """ - +# %% Constant strings to match code lines in the source code. DATA_CODE_STR = "# OPTIGUIDE DATA CODE GOES HERE" CONSTRAINT_CODE_STR = "# OPTIGUIDE CONSTRAINT CODE GOES HERE" # %% class OptiGuideAgent(Agent): - """OptiGuide is an agent to write Python code and to answer users - questions for supply chain-related coding project. + """(Experimental) OptiGuide is an agent to write Python code and to answer + users questions for supply chain-related coding project. Here, the OptiGuide agent manages three agents (coder, safeguard, and interpreter) and two assistant agents (pencil and shield). @@ -162,6 +163,8 @@ def receive(self, message: Union[Dict, str], sender): print(colored(new_code, "green")) # Step 3: safeguard + # FYI: in production, there are some other steps to check the code; for instance, + # with a static code analyzer or some other external packages. safeguard.send(message=SAFEGUARD_PROMPT.format(code=new_code), recipient=shield) safe_msg = safeguard.oai_conversations[shield.name][-1]["content"] print("Safety:", colored(safe_msg, "blue")) @@ -186,17 +189,7 @@ def receive(self, message: Union[Dict, str], sender): if success: # Step 6 - 7: interpret results - interpret_msg = f"""To answer the question: {question} - - I wrote the code: {new_code} - - Here are the execution results: {execution_rst} - - Can you try to organize these information to a human readable answer? - Remember to compare the new results to the original results you obtained in the beginning. - - --- HUMAN READABLE ANSWER --- - """ + interpret_msg = INTERPRETER_PROMT.format(execution_rst=execution_rst) interpreter.send(message=interpret_msg, recipient=pencil) reply = interpreter.oai_conversations[pencil.name][-1]["content"] else: @@ -213,7 +206,7 @@ def receive(self, message: Union[Dict, str], sender): # This approach replicate the evaluation section of the OptiGuide paper. -def _run_with_exec(src_code: str) -> object: +def _run_with_exec(src_code: str) -> Union[str, Exception]: """Run the code snippet with exec. Args: @@ -338,3 +331,11 @@ def _insert_code(src_code: str, new_lines: str) -> str: --- One-Word Answer: SAFE or DANGER --- """ + +INTERPRETER_PROMT = """Here are the execution results: {execution_rst} + +Can you organize these information to a human readable answer? +Remember to compare the new results to the original results you obtained in the beginning. + +--- HUMAN READABLE ANSWER --- +""" diff --git a/notebook/autogen_optiguide.ipynb b/notebook/autogen_optiguide.ipynb index d70a245223..ebf74b666e 100644 --- a/notebook/autogen_optiguide.ipynb +++ b/notebook/autogen_optiguide.ipynb @@ -14,9 +14,53 @@ "id": "59a05fc7", "metadata": {}, "source": [ - "Here we give a \"zero-shot\" learning example." + "Here we give a simple example, as designed and illustrated in the [OptiGuide paper](https://arxiv.org/abs/2307.03875).\n", + "While the original paper is designed specifically for supply chain optimization, the general framework can be easily adapted to other applications with coding capacity.\n", + "\n", + "\n" ] }, + { + "cell_type": "markdown", + "id": "5e92d200", + "metadata": {}, + "source": [ + "## OptiGuide for Supply Chain Optimization\n", + "The original system design for OptiGuide is \n", + "\n", + "![optiguide system](https://www.beibinli.com/docs/optiguide/optiguide_system.png)\n" + ] + }, + { + "cell_type": "markdown", + "id": "18c316b5", + "metadata": {}, + "source": [ + "## OptiGuide in Autogen\n", + "\n", + "With autogen, we redesign the framework as follow.\n", + "\n", + "Note that we have\n", + "\n", + "![optiguide system](https://www.beibinli.com/docs/optiguide/autogen_optiguide_sys.png)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "161ec2a5", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3606facd", + "metadata": {}, + "outputs": [], + "source": [] + }, { "cell_type": "code", "execution_count": 1, @@ -46,7 +90,16 @@ "from eventlet.timeout import Timeout\n", "\n", "import re\n", - "import requests # for loading the example source code\n" + "import requests # for loading the example source code\n", + "\n", + "import openai\n", + "import os\n", + "\n", + "from flaml.autogen.agent import Agent\n", + "from flaml.autogen.agent.opti_guide import OptiGuideAgent\n", + "\n", + "\n", + "from flaml import oai" ] }, { @@ -56,11 +109,6 @@ "metadata": {}, "outputs": [], "source": [ - "from flaml.autogen.agent import Agent\n", - "from flaml.autogen.agent.opti_guide import OptiGuideAgent\n", - "\n", - "\n", - "from flaml import oai\n", "config_list = oai.config_list_gpt4_gpt35()\n", "oai.ChatCompletion.start_logging()\n", "\n" @@ -73,150 +121,71 @@ "metadata": {}, "outputs": [], "source": [ - "import openai\n", - "import os\n", - "\n", "openai.api_type = \"azure\"\n", - "openai.api_base = \"https://msrcore.openai.azure.com\"\n", "openai.api_version = \"2023-03-15-preview\"\n", - "openai.api_key = os.getenv(\"CORE_AZURE_KEY\").strip().rstrip()" + "\n", + "openai.api_base = \"https://your.api.address.here.com\"\n", + "openai.api_key = \"\"" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "ca962ac5", "metadata": {}, - "outputs": [], - "source": [] + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "import time\n", + "\n", + "from gurobipy import GRB, Model\n", + "\n", + "# Example data\n", + "\n", + "capacity_in_supplier = {'supplier1': 150, 'supplier2': 50, 'supplier3': 100}\n", + "\n", + "shipping_cost_from_supplier_to_roastery = {\n", + ".\n", + ".\n", + ".\n", + "\n", + "m.update()\n", + "model.optimize()\n", + "\n", + "print(time.ctime())\n", + "if m.status == GRB.OPTIMAL:\n", + " print(f'Optimal cost: {m.objVal}')\n", + "else:\n", + " print(\"Not solved to optimality. Optimization status:\", m.status)\n", + "\n", + "\n" + ] + } + ], + "source": [ + "# Get the source code of our coffee example\n", + "code_url = \"https://www.beibinli.com/docs/optiguide/coffee.py\"\n", + "\n", + "code = requests.get(code_url).text\n", + "\n", + "# show the first head and tail of the source code\n", + "print(\"\\n\".join(code.split(\"\\n\")[:10]))\n", + "print(\".\\n\" * 3)\n", + "print(\"\\n\".join(code.split(\"\\n\")[-10:]))" + ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "e2d8dce8", "metadata": { "code_folding": [] }, "outputs": [], - "source": [ - "# Load the Coffee example\n", - "code = \"\"\"\n", - "\n", - "import time\n", - "\n", - "from gurobipy import GRB, Model\n", - "\n", - "# Example data\n", - "\n", - "capacity_in_supplier = {'supplier1': 150, 'supplier2': 50, 'supplier3': 100}\n", - "\n", - "shipping_cost_from_supplier_to_roastery = {\n", - " ('supplier1', 'roastery1'): 5,\n", - " ('supplier1', 'roastery2'): 4,\n", - " ('supplier2', 'roastery1'): 6,\n", - " ('supplier2', 'roastery2'): 3,\n", - " ('supplier3', 'roastery1'): 2,\n", - " ('supplier3', 'roastery2'): 7\n", - "}\n", - "\n", - "roasting_cost_light = {'roastery1': 3, 'roastery2': 5}\n", - "\n", - "roasting_cost_dark = {'roastery1': 5, 'roastery2': 6}\n", - "\n", - "shipping_cost_from_roastery_to_cafe = {\n", - " ('roastery1', 'cafe1'): 5,\n", - " ('roastery1', 'cafe2'): 3,\n", - " ('roastery1', 'cafe3'): 6,\n", - " ('roastery2', 'cafe1'): 4,\n", - " ('roastery2', 'cafe2'): 5,\n", - " ('roastery2', 'cafe3'): 2\n", - "}\n", - "\n", - "light_coffee_needed_for_cafe = {'cafe1': 20, 'cafe2': 30, 'cafe3': 40}\n", - "\n", - "dark_coffee_needed_for_cafe = {'cafe1': 20, 'cafe2': 20, 'cafe3': 100}\n", - "\n", - "cafes = list(set(i[1] for i in shipping_cost_from_roastery_to_cafe.keys()))\n", - "roasteries = list(\n", - " set(i[1] for i in shipping_cost_from_supplier_to_roastery.keys()))\n", - "suppliers = list(\n", - " set(i[0] for i in shipping_cost_from_supplier_to_roastery.keys()))\n", - "\n", - "\n", - "# Create a new model\n", - "model = Model(\"coffee_distribution\")\n", - "m = model\n", - "\n", - "# OPTIGUIDE DATA CODE GOES HERE\n", - "\n", - "\n", - "# Create variables\n", - "x = model.addVars(shipping_cost_from_supplier_to_roastery.keys(),\n", - " vtype=GRB.INTEGER,\n", - " name=\"x\")\n", - "y_light = model.addVars(shipping_cost_from_roastery_to_cafe.keys(),\n", - " vtype=GRB.INTEGER,\n", - " name=\"y_light\")\n", - "y_dark = model.addVars(shipping_cost_from_roastery_to_cafe.keys(),\n", - " vtype=GRB.INTEGER,\n", - " name=\"y_dark\")\n", - "\n", - "# Set objective\n", - "model.setObjective(\n", - " sum(x[i] * shipping_cost_from_supplier_to_roastery[i]\n", - " for i in shipping_cost_from_supplier_to_roastery.keys()) +\n", - " sum(roasting_cost_light[r] * y_light[r, c] +\n", - " roasting_cost_dark[r] * y_dark[r, c]\n", - " for r, c in shipping_cost_from_roastery_to_cafe.keys()) + sum(\n", - " (y_light[j] + y_dark[j]) * shipping_cost_from_roastery_to_cafe[j]\n", - " for j in shipping_cost_from_roastery_to_cafe.keys()), GRB.MINIMIZE)\n", - "\n", - "# Conservation of flow constraint\n", - "for r in set(i[1] for i in shipping_cost_from_supplier_to_roastery.keys()):\n", - " model.addConstr(\n", - " sum(x[i]\n", - " for i in shipping_cost_from_supplier_to_roastery.keys()\n", - " if i[1] == r) == sum(\n", - " y_light[j] + y_dark[j]\n", - " for j in shipping_cost_from_roastery_to_cafe.keys()\n", - " if j[0] == r), f\"flow_{r}\")\n", - "\n", - "# Add supply constraints\n", - "for s in set(i[0] for i in shipping_cost_from_supplier_to_roastery.keys()):\n", - " model.addConstr(\n", - " sum(x[i]\n", - " for i in shipping_cost_from_supplier_to_roastery.keys()\n", - " if i[0] == s) <= capacity_in_supplier[s], f\"supply_{s}\")\n", - "\n", - "# Add demand constraints\n", - "for c in set(i[1] for i in shipping_cost_from_roastery_to_cafe.keys()):\n", - " model.addConstr(\n", - " sum(y_light[j]\n", - " for j in shipping_cost_from_roastery_to_cafe.keys()\n", - " if j[1] == c) >= light_coffee_needed_for_cafe[c],\n", - " f\"light_demand_{c}\")\n", - " model.addConstr(\n", - " sum(y_dark[j]\n", - " for j in shipping_cost_from_roastery_to_cafe.keys()\n", - " if j[1] == c) >= dark_coffee_needed_for_cafe[c], f\"dark_demand_{c}\")\n", - "\n", - "# Optimize model\n", - "model.optimize()\n", - "\n", - "# OPTIGUIDE CONSTRAINT CODE GOES HERE\n", - "\n", - "# Solve\n", - "m.update()\n", - "model.optimize()\n", - "\n", - "print(time.ctime())\n", - "if m.status == GRB.OPTIMAL:\n", - " print(f'Optimal cost: {m.objVal}')\n", - "else:\n", - " print(\"Not solved to optimality. Optimization status:\", m.status)\n", - "\n", - "\"\"\"" - ] + "source": [] }, { "cell_type": "code", @@ -227,6 +196,7 @@ }, "outputs": [], "source": [ + "# In-context learning examples.\n", "example_qa = \"\"\"\n", "----------\n", "Question: Why is it not recommended to use just one supplier for roastery 2?\n", @@ -280,18 +250,18 @@ "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)\n", "Thread count: 64 physical cores, 128 logical processors, using up to 32 threads\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0xd4254685\n", + "Model fingerprint: 0x1816ab29\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", " Objective range [2e+00, 1e+01]\n", " Bounds range [0e+00, 0e+00]\n", " RHS range [2e+01, 2e+02]\n", - "Found heuristic solution: objective 3490.0000000\n", + "Found heuristic solution: objective 2900.0000000\n", "Presolve time: 0.00s\n", "Presolved: 11 rows, 18 columns, 36 nonzeros\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", - "Found heuristic solution: objective 3486.0000000\n", + "Found heuristic solution: objective 2896.0000000\n", "\n", "Root relaxation: objective 2.470000e+03, 11 iterations, 0.00 seconds (0.00 work units)\n", "\n", @@ -300,17 +270,17 @@ "\n", "* 0 0 0 2470.0000000 2470.00000 0.00% - 0s\n", "\n", - "Explored 1 nodes (11 simplex iterations) in 0.02 seconds (0.00 work units)\n", + "Explored 1 nodes (11 simplex iterations) in 0.01 seconds (0.00 work units)\n", "Thread count was 32 (of 128 available processors)\n", "\n", - "Solution count 3: 2470 3486 3490 \n", + "Solution count 3: 2470 2896 2900 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)\n", "Thread count: 64 physical cores, 128 logical processors, using up to 32 threads\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0xd4254685\n", + "Model fingerprint: 0x1816ab29\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", @@ -325,11 +295,11 @@ "Explored 1 nodes (11 simplex iterations) in 0.01 seconds (0.00 work units)\n", "Thread count was 32 (of 128 available processors)\n", "\n", - "Solution count 3: 2470 3486 3490 \n", + "Solution count 3: 2470 2896 2900 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", - "Tue Jul 25 12:18:45 2023\n", + "Tue Jul 25 15:25:02 2023\n", "Optimal cost: 2470.0\n" ] } @@ -337,11 +307,9 @@ "source": [ "agent = OptiGuideAgent(name=\"OptiGuide Coffee Example\", \n", " source_code=code,\n", - "# doc_str=\"\", \n", " example_qa=\"\")\n", "\n", - "user = Agent(\"user\", system_message=\"\")\n", - "# user.organize_memory(agent)" + "user = Agent(\"user\", system_message=\"\")\n" ] }, { @@ -379,30 +347,24 @@ "--------------------------------------------------------------------------------\n", "pencil (to coder interpreter):\n", "\n", + "To prohibit shipping from supplier 1 to roastery 2, we can add a constraint that sets the corresponding value of the decision variable x to 0. Here's the code snippet you need to add:\n", + "\n", + "```python\n", "# Prohibit shipping from supplier 1 to roastery 2\n", - "model.addConstr(x[('supplier1', 'roastery2')] == 0, \"No_Shipping_S1R2\")\n", + "model.addConstr(x[(\"supplier1\", \"roastery2\")] == 0, \"prohibit_s1toR2\")\n", + "```\n", "\n", - "# Solve after the new constraint\n", - "model.update()\n", - "model.optimize()\n", + "This constraint makes sure that there is no flow of coffee beans from supplier 1 to roastery 2 in the solution.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[32m# Prohibit shipping from supplier 1 to roastery 2\n", - "model.addConstr(x[('supplier1', 'roastery2')] == 0, \"No_Shipping_S1R2\")\n", - "\n", - "# Solve after the new constraint\n", - "model.update()\n", - "model.optimize()\u001b[0m\n", + "model.addConstr(x[(\"supplier1\", \"roastery2\")] == 0, \"prohibit_s1toR2\")\u001b[0m\n", "safeguard (to shield):\n", "\n", "\n", "--- Code ---\n", "# Prohibit shipping from supplier 1 to roastery 2\n", - "model.addConstr(x[('supplier1', 'roastery2')] == 0, \"No_Shipping_S1R2\")\n", - "\n", - "# Solve after the new constraint\n", - "model.update()\n", - "model.optimize()\n", + "model.addConstr(x[(\"supplier1\", \"roastery2\")] == 0, \"prohibit_s1toR2\")\n", "\n", "--- One-Word Answer: SAFE or DANGER ---\n", "\n", @@ -417,18 +379,18 @@ "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)\n", "Thread count: 64 physical cores, 128 logical processors, using up to 32 threads\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0xd4254685\n", + "Model fingerprint: 0x1816ab29\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", " Objective range [2e+00, 1e+01]\n", " Bounds range [0e+00, 0e+00]\n", " RHS range [2e+01, 2e+02]\n", - "Found heuristic solution: objective 3490.0000000\n", + "Found heuristic solution: objective 2900.0000000\n", "Presolve time: 0.00s\n", "Presolved: 11 rows, 18 columns, 36 nonzeros\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", - "Found heuristic solution: objective 3486.0000000\n", + "Found heuristic solution: objective 2896.0000000\n", "\n", "Root relaxation: objective 2.470000e+03, 11 iterations, 0.00 seconds (0.00 work units)\n", "\n", @@ -440,14 +402,14 @@ "Explored 1 nodes (11 simplex iterations) in 0.01 seconds (0.00 work units)\n", "Thread count was 32 (of 128 available processors)\n", "\n", - "Solution count 3: 2470 3486 3490 \n", + "Solution count 3: 2470 2896 2900 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)\n", "Thread count: 64 physical cores, 128 logical processors, using up to 32 threads\n", "Optimize a model with 12 rows, 18 columns and 37 nonzeros\n", - "Model fingerprint: 0xe6485c72\n", + "Model fingerprint: 0x343ecbc0\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", @@ -456,14 +418,13 @@ " RHS range [2e+01, 2e+02]\n", "\n", "MIP start from previous solve did not produce a new incumbent solution\n", - "MIP start from previous solve violates constraint No_Shipping_S1R2 by 80.000000000\n", + "MIP start from previous solve violates constraint prohibit_s1toR2 by 80.000000000\n", "\n", - "Found heuristic solution: objective 3490.0000000\n", + "Found heuristic solution: objective 2920.0000000\n", "Presolve removed 2 rows and 1 columns\n", "Presolve time: 0.00s\n", "Presolved: 10 rows, 17 columns, 33 nonzeros\n", "Variable types: 0 continuous, 17 integer (0 binary)\n", - "Found heuristic solution: objective 3485.0000000\n", "\n", "Root relaxation: objective 2.760000e+03, 10 iterations, 0.00 seconds (0.00 work units)\n", "\n", @@ -475,63 +436,32 @@ "Explored 1 nodes (10 simplex iterations) in 0.01 seconds (0.00 work units)\n", "Thread count was 32 (of 128 available processors)\n", "\n", - "Solution count 3: 2760 3485 3490 \n", - "\n", - "Optimal solution found (tolerance 1.00e-04)\n", - "Best objective 2.760000000000e+03, best bound 2.760000000000e+03, gap 0.0000%\n", - "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)\n", - "Thread count: 64 physical cores, 128 logical processors, using up to 32 threads\n", - "Optimize a model with 12 rows, 18 columns and 37 nonzeros\n", - "Model fingerprint: 0xe6485c72\n", - "Variable types: 0 continuous, 18 integer (0 binary)\n", - "Coefficient statistics:\n", - " Matrix range [1e+00, 1e+00]\n", - " Objective range [2e+00, 1e+01]\n", - " Bounds range [0e+00, 0e+00]\n", - " RHS range [2e+01, 2e+02]\n", - "Presolved: 10 rows, 17 columns, 33 nonzeros\n", - "\n", - "Continuing optimization...\n", - "\n", - "\n", - "Explored 1 nodes (10 simplex iterations) in 0.00 seconds (0.00 work units)\n", - "Thread count was 32 (of 128 available processors)\n", - "\n", - "Solution count 3: 2760 3485 3490 \n", + "Solution count 2: 2760 2920 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 2.760000000000e+03, best bound 2.760000000000e+03, gap 0.0000%\n", - "Tue Jul 25 12:18:45 2023\n", + "Tue Jul 25 15:25:02 2023\n", "Optimal cost: 2760.0\n", "\u001b[33mOptimization problem solved. The objective value is: 2760.0\u001b[0m\n", "coder interpreter (to pencil):\n", "\n", - "To answer the question: What if we prohibit shipping from supplier 1 to roastery 2?\n", + "Here are the execution results: Optimization problem solved. The objective value is: 2760.0\n", "\n", - " I wrote the code: # Prohibit shipping from supplier 1 to roastery 2\n", - "model.addConstr(x[('supplier1', 'roastery2')] == 0, \"No_Shipping_S1R2\")\n", + "Can you organize these information to a human readable answer?\n", + "Remember to compare the new results to the original results you obtained in the beginning.\n", "\n", - "# Solve after the new constraint\n", - "model.update()\n", - "model.optimize()\n", - "\n", - " Here are the execution results: Optimization problem solved. The objective value is: 2760.0\n", - "\n", - " Can you try to organize these information to a human readable answer?\n", - " Remember to compare the new results to the original results you obtained in the beginning.\n", + "--- HUMAN READABLE ANSWER ---\n", "\n", - " --- HUMAN READABLE ANSWER ---\n", - " \n", "\n", "--------------------------------------------------------------------------------\n", "pencil (to coder interpreter):\n", "\n", - "After prohibiting the shipping from supplier 1 to roastery 2 and re-optimizing the model, the new objective value is 2760.0. This is an increase of 290.0 compared to the original objective value of 2470.0. This means that the total cost to fulfill the supply chain requirements has increased due to the additional constraint we imposed.\n", + "After prohibiting shipping from supplier 1 to roastery 2 and re-solving the optimization problem, the new optimal cost is 2760.0. This is an increase compared to the original optimal cost of 2470.0. The restriction has led to a higher cost in the coffee distribution network.\n", "\n", "--------------------------------------------------------------------------------\n", "OptiGuide Coffee Example (to user):\n", "\n", - "After prohibiting the shipping from supplier 1 to roastery 2 and re-optimizing the model, the new objective value is 2760.0. This is an increase of 290.0 compared to the original objective value of 2470.0. This means that the total cost to fulfill the supply chain requirements has increased due to the additional constraint we imposed.\n", + "After prohibiting shipping from supplier 1 to roastery 2 and re-solving the optimization problem, the new optimal cost is 2760.0. This is an increase compared to the original optimal cost of 2470.0. The restriction has led to a higher cost in the coffee distribution network.\n", "\n", "--------------------------------------------------------------------------------\n" ] @@ -568,31 +498,59 @@ "--------------------------------------------------------------------------------\n", "pencil (to coder interpreter):\n", "\n", - "To examine the impact of supplier1 being able to supply only half the quantity, we can update the capacity in the `capacity_in_supplier` dictionary for supplier1 and reoptimize the model.\n", - "\n", - "Here's the code snippet to update the capacity of supplier1:\n", + "To account for supplier1 being able to supply only half the capacity, you can update the capacity constraint for supplier1:\n", "\n", "```python\n", - "capacity_in_supplier['supplier1'] = capacity_in_supplier['supplier1'] // 2\n", - "model.update()\n", - "\n", + "# Update supplier1's capacity to half of the original value\n", + "capacity_in_supplier[\"supplier1\"] = capacity_in_supplier[\"supplier1\"] // 2\n", + "\n", + "# Update the supply constraint for supplier1\n", + "m.remove(m.getConstrByName(\"supply_supplier1\"))\n", + "m.addConstr(\n", + " sum(x[i]\n", + " for i in shipping_cost_from_supplier_to_roastery.keys()\n", + " if i[0] == \"supplier1\") <= capacity_in_supplier[\"supplier1\"],\n", + " \"supply_supplier1\")\n", + "\n", + "# Re-optimize the model\n", + "m.update()\n", "model.optimize()\n", "```\n", "\n", - "Add this code snippet to the lines with substring \"# OPTIGUIDE *** CODE GOES HERE\" and check the optimization result to see the impact on the objective value.\n", + "After updating the capacity of supplier1 and re-solving the optimization problem, you can check the new optimal cost to evaluate the impact of the changes.\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[32mcapacity_in_supplier['supplier1'] = capacity_in_supplier['supplier1'] // 2\n", - "model.update()\n", - "\n", + "\u001b[32m# Update supplier1's capacity to half of the original value\n", + "capacity_in_supplier[\"supplier1\"] = capacity_in_supplier[\"supplier1\"] // 2\n", + "\n", + "# Update the supply constraint for supplier1\n", + "m.remove(m.getConstrByName(\"supply_supplier1\"))\n", + "m.addConstr(\n", + " sum(x[i]\n", + " for i in shipping_cost_from_supplier_to_roastery.keys()\n", + " if i[0] == \"supplier1\") <= capacity_in_supplier[\"supplier1\"],\n", + " \"supply_supplier1\")\n", + "\n", + "# Re-optimize the model\n", + "m.update()\n", "model.optimize()\u001b[0m\n", "safeguard (to shield):\n", "\n", "\n", "--- Code ---\n", - "capacity_in_supplier['supplier1'] = capacity_in_supplier['supplier1'] // 2\n", - "model.update()\n", - "\n", + "# Update supplier1's capacity to half of the original value\n", + "capacity_in_supplier[\"supplier1\"] = capacity_in_supplier[\"supplier1\"] // 2\n", + "\n", + "# Update the supply constraint for supplier1\n", + "m.remove(m.getConstrByName(\"supply_supplier1\"))\n", + "m.addConstr(\n", + " sum(x[i]\n", + " for i in shipping_cost_from_supplier_to_roastery.keys()\n", + " if i[0] == \"supplier1\") <= capacity_in_supplier[\"supplier1\"],\n", + " \"supply_supplier1\")\n", + "\n", + "# Re-optimize the model\n", + "m.update()\n", "model.optimize()\n", "\n", "--- One-Word Answer: SAFE or DANGER ---\n", @@ -607,30 +565,48 @@ "Safety: \u001b[34mSAFE\u001b[0m\n", "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)\n", "Thread count: 64 physical cores, 128 logical processors, using up to 32 threads\n", - "Optimize a model with 0 rows, 0 columns and 0 nonzeros\n", - "Model fingerprint: 0xf9715da1\n", + "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", + "Model fingerprint: 0x1816ab29\n", + "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", - " Matrix range [0e+00, 0e+00]\n", - " Objective range [0e+00, 0e+00]\n", + " Matrix range [1e+00, 1e+00]\n", + " Objective range [2e+00, 1e+01]\n", " Bounds range [0e+00, 0e+00]\n", - " RHS range [0e+00, 0e+00]\n", + " RHS range [2e+01, 2e+02]\n", + "Found heuristic solution: objective 2900.0000000\n", "Presolve time: 0.00s\n", - "Presolve: All rows and columns removed\n", - "Iteration Objective Primal Inf. Dual Inf. Time\n", - " 0 0.0000000e+00 0.000000e+00 0.000000e+00 0s\n", + "Presolved: 11 rows, 18 columns, 36 nonzeros\n", + "Variable types: 0 continuous, 18 integer (0 binary)\n", + "Found heuristic solution: objective 2896.0000000\n", + "\n", + "Root relaxation: objective 2.470000e+03, 11 iterations, 0.00 seconds (0.00 work units)\n", + "\n", + " Nodes | Current Node | Objective Bounds | Work\n", + " Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n", + "\n", + "* 0 0 0 2470.0000000 2470.00000 0.00% - 0s\n", "\n", - "Solved in 0 iterations and 0.01 seconds (0.00 work units)\n", - "Optimal objective 0.000000000e+00\n", + "Explored 1 nodes (11 simplex iterations) in 0.01 seconds (0.00 work units)\n", + "Thread count was 32 (of 128 available processors)\n", + "\n", + "Solution count 3: 2470 2896 2900 \n", + "\n", + "Optimal solution found (tolerance 1.00e-04)\n", + "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)\n", "Thread count: 64 physical cores, 128 logical processors, using up to 32 threads\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0x081d397e\n", + "Model fingerprint: 0xaed0fb9c\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", " Objective range [2e+00, 1e+01]\n", " Bounds range [0e+00, 0e+00]\n", " RHS range [2e+01, 1e+02]\n", + "\n", + "MIP start from previous solve did not produce a new incumbent solution\n", + "MIP start from previous solve violates constraint supply_supplier1 by 5.000000000\n", + "\n", "Presolve time: 0.00s\n", "Presolved: 11 rows, 18 columns, 36 nonzeros\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", @@ -652,7 +628,7 @@ "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)\n", "Thread count: 64 physical cores, 128 logical processors, using up to 32 threads\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0x081d397e\n", + "Model fingerprint: 0xaed0fb9c\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", @@ -664,42 +640,39 @@ "Continuing optimization...\n", "\n", "\n", - "Explored 1 nodes (14 simplex iterations) in 0.01 seconds (0.00 work units)\n", + "Explored 1 nodes (14 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 32 (of 128 available processors)\n", "\n", "Solution count 0\n", "\n", "Model is infeasible\n", "Best objective -, best bound -, gap -\n", - "Tue Jul 25 12:18:45 2023\n", + "Tue Jul 25 15:25:02 2023\n", "Not solved to optimality. Optimization status: 3\n", "\u001b[33minfeasible\u001b[0m\n", "coder interpreter (to pencil):\n", "\n", - "To answer the question: What is the impact of supplier1 being able to supply only half the quantity at present?\n", + "Here are the execution results: infeasible\n", "\n", - " I wrote the code: capacity_in_supplier['supplier1'] = capacity_in_supplier['supplier1'] // 2\n", - "model.update()\n", + "Can you organize these information to a human readable answer?\n", + "Remember to compare the new results to the original results you obtained in the beginning.\n", "\n", - "model.optimize()\n", - "\n", - " Here are the execution results: infeasible\n", - "\n", - " Can you try to organize these information to a human readable answer?\n", - " Remember to compare the new results to the original results you obtained in the beginning.\n", + "--- HUMAN READABLE ANSWER ---\n", "\n", - " --- HUMAN READABLE ANSWER ---\n", - " \n", "\n", "--------------------------------------------------------------------------------\n", "pencil (to coder interpreter):\n", "\n", - "After updating the capacity of supplier1 to supply only half the quantity and re-optimizing the model, the optimization status indicates that the new problem is infeasible. This means that it is not possible to satisfy the supply chain requirements under the given constraints when supplier1 can supply only half of its original capacity. The original optimal objective value was 2470.0; however, due to the capacity constraint, the model cannot find a feasible solution anymore.\n", + "After reducing supplier1's capacity to half of its original value, the optimization problem became infeasible. This means that with the new capacity constraint, it is not possible to satisfy all the demand for light and dark coffee at the cafes while maintaining the existing constraints.\n", + "\n", + "The original optimal cost for the distribution network was 2470.0. However, with the updated supplier1 capacity constraint, the model can no longer find an optimal solution, indicating that the current network cannot meet the coffee demands under these conditions.\n", "\n", "--------------------------------------------------------------------------------\n", "OptiGuide Coffee Example (to user):\n", "\n", - "After updating the capacity of supplier1 to supply only half the quantity and re-optimizing the model, the optimization status indicates that the new problem is infeasible. This means that it is not possible to satisfy the supply chain requirements under the given constraints when supplier1 can supply only half of its original capacity. The original optimal objective value was 2470.0; however, due to the capacity constraint, the model cannot find a feasible solution anymore.\n", + "After reducing supplier1's capacity to half of its original value, the optimization problem became infeasible. This means that with the new capacity constraint, it is not possible to satisfy all the demand for light and dark coffee at the cafes while maintaining the existing constraints.\n", + "\n", + "The original optimal cost for the distribution network was 2470.0. However, with the updated supplier1 capacity constraint, the model can no longer find an optimal solution, indicating that the current network cannot meet the coffee demands under these conditions.\n", "\n", "--------------------------------------------------------------------------------\n" ] From a6a698456690c3ebcdc81e7ab153827834c5f3ad Mon Sep 17 00:00:00 2001 From: Beibin Li Date: Tue, 25 Jul 2023 20:26:47 -0700 Subject: [PATCH 3/5] Update name: Agent -> GenericAgent --- flaml/autogen/agent/opti_guide.py | 13 +- notebook/autogen_optiguide.ipynb | 237 ++++++++++++------------------ 2 files changed, 100 insertions(+), 150 deletions(-) diff --git a/flaml/autogen/agent/opti_guide.py b/flaml/autogen/agent/opti_guide.py index f794840bd4..28bd867182 100644 --- a/flaml/autogen/agent/opti_guide.py +++ b/flaml/autogen/agent/opti_guide.py @@ -20,16 +20,17 @@ import gurobipy as gp from eventlet.timeout import Timeout -from flaml import oai -from flaml.autogen.code_utils import DEFAULT_MODEL, extract_code from gurobipy import GRB from termcolor import colored +from flaml import oai +from flaml.autogen.code_utils import DEFAULT_MODEL, extract_code + from .agent import Agent from .assistant_agent import AssistantAgent +from .generic_agent import GenericAgent from .user_proxy_agent import UserProxyAgent - # %% System Messages ASSIST_SYSTEM_MSG = """You are OptiGuide, an agent to write Python code and to answer users questions for supply chain-related coding project. @@ -77,7 +78,7 @@ # %% -class OptiGuideAgent(Agent): +class OptiGuideAgent(GenericAgent): """(Experimental) OptiGuide is an agent to write Python code and to answer users questions for supply chain-related coding project. @@ -98,7 +99,9 @@ def __init__(self, name, source_code, doc_str="", example_qa="", **config): assert source_code.find(DATA_CODE_STR) >= 0, "DATA_CODE_STR not found." assert source_code.find(CONSTRAINT_CODE_STR) >= 0, "CONSTRAINT_CODE_STR not found." - super().__init__(name, system_message="") + super().__init__( + name, max_consecutive_auto_reply=0, human_input_mode="NEVER", code_execution_config=False, **config + ) self._source_code = source_code self._sender_dict = {} diff --git a/notebook/autogen_optiguide.ipynb b/notebook/autogen_optiguide.ipynb index ebf74b666e..684ed6b88c 100644 --- a/notebook/autogen_optiguide.ipynb +++ b/notebook/autogen_optiguide.ipynb @@ -89,17 +89,17 @@ "from gurobipy import GRB\n", "from eventlet.timeout import Timeout\n", "\n", + "# import auxillary packages\n", "import re\n", "import requests # for loading the example source code\n", - "\n", "import openai\n", "import os\n", "\n", - "from flaml.autogen.agent import Agent\n", - "from flaml.autogen.agent.opti_guide import OptiGuideAgent\n", - "\n", "\n", - "from flaml import oai" + "# import flaml and autogen\n", + "from flaml import oai\n", + "from flaml.autogen.agent import Agent, GenericAgent\n", + "from flaml.autogen.agent.opti_guide import OptiGuideAgent" ] }, { @@ -123,9 +123,13 @@ "source": [ "openai.api_type = \"azure\"\n", "openai.api_version = \"2023-03-15-preview\"\n", - "\n", "openai.api_base = \"https://your.api.address.here.com\"\n", - "openai.api_key = \"\"" + "openai.api_key = \"\"\n", + "\n", + "openai.api_type = \"azure\"\n", + "openai.api_base = \"https://msrcore.openai.azure.com\"\n", + "openai.api_version = \"2023-03-15-preview\"\n", + "openai.api_key = os.getenv(\"CORE_AZURE_KEY\")" ] }, { @@ -241,75 +245,15 @@ "execution_count": 7, "id": "af53727c", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Set parameter TokenServer to value \"ishai-z420\"\n", - "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)\n", - "Thread count: 64 physical cores, 128 logical processors, using up to 32 threads\n", - "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0x1816ab29\n", - "Variable types: 0 continuous, 18 integer (0 binary)\n", - "Coefficient statistics:\n", - " Matrix range [1e+00, 1e+00]\n", - " Objective range [2e+00, 1e+01]\n", - " Bounds range [0e+00, 0e+00]\n", - " RHS range [2e+01, 2e+02]\n", - "Found heuristic solution: objective 2900.0000000\n", - "Presolve time: 0.00s\n", - "Presolved: 11 rows, 18 columns, 36 nonzeros\n", - "Variable types: 0 continuous, 18 integer (0 binary)\n", - "Found heuristic solution: objective 2896.0000000\n", - "\n", - "Root relaxation: objective 2.470000e+03, 11 iterations, 0.00 seconds (0.00 work units)\n", - "\n", - " Nodes | Current Node | Objective Bounds | Work\n", - " Expl Unexpl | Obj Depth IntInf | Incumbent BestBd Gap | It/Node Time\n", - "\n", - "* 0 0 0 2470.0000000 2470.00000 0.00% - 0s\n", - "\n", - "Explored 1 nodes (11 simplex iterations) in 0.01 seconds (0.00 work units)\n", - "Thread count was 32 (of 128 available processors)\n", - "\n", - "Solution count 3: 2470 2896 2900 \n", - "\n", - "Optimal solution found (tolerance 1.00e-04)\n", - "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", - "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)\n", - "Thread count: 64 physical cores, 128 logical processors, using up to 32 threads\n", - "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0x1816ab29\n", - "Variable types: 0 continuous, 18 integer (0 binary)\n", - "Coefficient statistics:\n", - " Matrix range [1e+00, 1e+00]\n", - " Objective range [2e+00, 1e+01]\n", - " Bounds range [0e+00, 0e+00]\n", - " RHS range [2e+01, 2e+02]\n", - "Presolved: 11 rows, 18 columns, 36 nonzeros\n", - "\n", - "Continuing optimization...\n", - "\n", - "\n", - "Explored 1 nodes (11 simplex iterations) in 0.01 seconds (0.00 work units)\n", - "Thread count was 32 (of 128 available processors)\n", - "\n", - "Solution count 3: 2470 2896 2900 \n", - "\n", - "Optimal solution found (tolerance 1.00e-04)\n", - "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", - "Tue Jul 25 15:25:02 2023\n", - "Optimal cost: 2470.0\n" - ] - } - ], + "outputs": [], "source": [ + "%%capture\n", "agent = OptiGuideAgent(name=\"OptiGuide Coffee Example\", \n", " source_code=code,\n", " example_qa=\"\")\n", "\n", - "user = Agent(\"user\", system_message=\"\")\n" + "user = GenericAgent(\"user\", max_consecutive_auto_reply=0,\n", + " human_input_mode=\"NEVER\", code_execution_config=False)\n" ] }, { @@ -347,24 +291,21 @@ "--------------------------------------------------------------------------------\n", "pencil (to coder interpreter):\n", "\n", - "To prohibit shipping from supplier 1 to roastery 2, we can add a constraint that sets the corresponding value of the decision variable x to 0. Here's the code snippet you need to add:\n", + "To prohibit shipping from supplier 1 to roastery 2, you can add a constraint to the model. Here's the code to do that:\n", "\n", "```python\n", - "# Prohibit shipping from supplier 1 to roastery 2\n", - "model.addConstr(x[(\"supplier1\", \"roastery2\")] == 0, \"prohibit_s1toR2\")\n", + "model.addConstr(x[('supplier1', 'roastery2')] == 0, \"prohibit_sup1_to_roast2\")\n", "```\n", "\n", - "This constraint makes sure that there is no flow of coffee beans from supplier 1 to roastery 2 in the solution.\n", + "You should insert this code snippet right before the `# Optimize model` line in the original code. This constraint will set the flow between supplier 1 and roastery 2 to zero, effectively prohibiting any shipping between those two locations.\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[32m# Prohibit shipping from supplier 1 to roastery 2\n", - "model.addConstr(x[(\"supplier1\", \"roastery2\")] == 0, \"prohibit_s1toR2\")\u001b[0m\n", + "\u001b[32mmodel.addConstr(x[('supplier1', 'roastery2')] == 0, \"prohibit_sup1_to_roast2\")\u001b[0m\n", "safeguard (to shield):\n", "\n", "\n", "--- Code ---\n", - "# Prohibit shipping from supplier 1 to roastery 2\n", - "model.addConstr(x[(\"supplier1\", \"roastery2\")] == 0, \"prohibit_s1toR2\")\n", + "model.addConstr(x[('supplier1', 'roastery2')] == 0, \"prohibit_sup1_to_roast2\")\n", "\n", "--- One-Word Answer: SAFE or DANGER ---\n", "\n", @@ -379,18 +320,18 @@ "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)\n", "Thread count: 64 physical cores, 128 logical processors, using up to 32 threads\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0x1816ab29\n", + "Model fingerprint: 0xb1e18c0e\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", " Objective range [2e+00, 1e+01]\n", " Bounds range [0e+00, 0e+00]\n", " RHS range [2e+01, 2e+02]\n", - "Found heuristic solution: objective 2900.0000000\n", + "Found heuristic solution: objective 3490.0000000\n", "Presolve time: 0.00s\n", "Presolved: 11 rows, 18 columns, 36 nonzeros\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", - "Found heuristic solution: objective 2896.0000000\n", + "Found heuristic solution: objective 3486.0000000\n", "\n", "Root relaxation: objective 2.470000e+03, 11 iterations, 0.00 seconds (0.00 work units)\n", "\n", @@ -402,14 +343,14 @@ "Explored 1 nodes (11 simplex iterations) in 0.01 seconds (0.00 work units)\n", "Thread count was 32 (of 128 available processors)\n", "\n", - "Solution count 3: 2470 2896 2900 \n", + "Solution count 3: 2470 3486 3490 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)\n", "Thread count: 64 physical cores, 128 logical processors, using up to 32 threads\n", "Optimize a model with 12 rows, 18 columns and 37 nonzeros\n", - "Model fingerprint: 0x343ecbc0\n", + "Model fingerprint: 0xb1400c60\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", @@ -418,13 +359,14 @@ " RHS range [2e+01, 2e+02]\n", "\n", "MIP start from previous solve did not produce a new incumbent solution\n", - "MIP start from previous solve violates constraint prohibit_s1toR2 by 80.000000000\n", + "MIP start from previous solve violates constraint prohibit_sup1_to_roast2 by 80.000000000\n", "\n", - "Found heuristic solution: objective 2920.0000000\n", + "Found heuristic solution: objective 3490.0000000\n", "Presolve removed 2 rows and 1 columns\n", "Presolve time: 0.00s\n", "Presolved: 10 rows, 17 columns, 33 nonzeros\n", "Variable types: 0 continuous, 17 integer (0 binary)\n", + "Found heuristic solution: objective 3485.0000000\n", "\n", "Root relaxation: objective 2.760000e+03, 10 iterations, 0.00 seconds (0.00 work units)\n", "\n", @@ -436,11 +378,11 @@ "Explored 1 nodes (10 simplex iterations) in 0.01 seconds (0.00 work units)\n", "Thread count was 32 (of 128 available processors)\n", "\n", - "Solution count 2: 2760 2920 \n", + "Solution count 3: 2760 3485 3490 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 2.760000000000e+03, best bound 2.760000000000e+03, gap 0.0000%\n", - "Tue Jul 25 15:25:02 2023\n", + "Tue Jul 25 20:25:47 2023\n", "Optimal cost: 2760.0\n", "\u001b[33mOptimization problem solved. The objective value is: 2760.0\u001b[0m\n", "coder interpreter (to pencil):\n", @@ -456,12 +398,16 @@ "--------------------------------------------------------------------------------\n", "pencil (to coder interpreter):\n", "\n", - "After prohibiting shipping from supplier 1 to roastery 2 and re-solving the optimization problem, the new optimal cost is 2760.0. This is an increase compared to the original optimal cost of 2470.0. The restriction has led to a higher cost in the coffee distribution network.\n", + "After adding the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was still solved successfully. The new objective value, which represents the minimum cost in this scenario, is 2760.0. \n", + "\n", + "Compared to the original results without the constraint, where the optimal cost was 2470.0, the new solution is more expensive by 290 units. This shows that prohibiting shipping from supplier 1 to roastery 2 increases the total cost of the coffee distribution.\n", "\n", "--------------------------------------------------------------------------------\n", "OptiGuide Coffee Example (to user):\n", "\n", - "After prohibiting shipping from supplier 1 to roastery 2 and re-solving the optimization problem, the new optimal cost is 2760.0. This is an increase compared to the original optimal cost of 2470.0. The restriction has led to a higher cost in the coffee distribution network.\n", + "After adding the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was still solved successfully. The new objective value, which represents the minimum cost in this scenario, is 2760.0. \n", + "\n", + "Compared to the original results without the constraint, where the optimal cost was 2470.0, the new solution is more expensive by 290 units. This shows that prohibiting shipping from supplier 1 to roastery 2 increases the total cost of the coffee distribution.\n", "\n", "--------------------------------------------------------------------------------\n" ] @@ -498,61 +444,61 @@ "--------------------------------------------------------------------------------\n", "pencil (to coder interpreter):\n", "\n", - "To account for supplier1 being able to supply only half the capacity, you can update the capacity constraint for supplier1:\n", + "# First, update the capacity of supplier1 by halving the current value\n", + "new_capacity_supplier1 = capacity_in_supplier['supplier1'] / 2\n", "\n", - "```python\n", - "# Update supplier1's capacity to half of the original value\n", - "capacity_in_supplier[\"supplier1\"] = capacity_in_supplier[\"supplier1\"] // 2\n", - "\n", - "# Update the supply constraint for supplier1\n", - "m.remove(m.getConstrByName(\"supply_supplier1\"))\n", - "m.addConstr(\n", - " sum(x[i]\n", - " for i in shipping_cost_from_supplier_to_roastery.keys()\n", - " if i[0] == \"supplier1\") <= capacity_in_supplier[\"supplier1\"],\n", - " \"supply_supplier1\")\n", - "\n", - "# Re-optimize the model\n", - "m.update()\n", + "# Then, update the supply constraint for supplier1\n", + "model.remove(model.getConstrByName(\"supply_supplier1\"))\n", + "model.addConstr(sum(x[i] for i in shipping_cost_from_supplier_to_roastery.keys() if i[0] == 'supplier1') <= new_capacity_supplier1, \"supply_supplier1\")\n", + "\n", + "# Solve the model again\n", "model.optimize()\n", - "```\n", "\n", - "After updating the capacity of supplier1 and re-solving the optimization problem, you can check the new optimal cost to evaluate the impact of the changes.\n", + "# If the model has an optimal solution, print the new objective value\n", + "if model.status == GRB.OPTIMAL:\n", + " new_objVal = model.objVal\n", + " print(f'New optimal cost with supplier1 halved capacity: {new_objVal}')\n", + "else:\n", + " print(\"Not solved to optimality. Optimization status:\", model.status)\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[32m# Update supplier1's capacity to half of the original value\n", - "capacity_in_supplier[\"supplier1\"] = capacity_in_supplier[\"supplier1\"] // 2\n", - "\n", - "# Update the supply constraint for supplier1\n", - "m.remove(m.getConstrByName(\"supply_supplier1\"))\n", - "m.addConstr(\n", - " sum(x[i]\n", - " for i in shipping_cost_from_supplier_to_roastery.keys()\n", - " if i[0] == \"supplier1\") <= capacity_in_supplier[\"supplier1\"],\n", - " \"supply_supplier1\")\n", - "\n", - "# Re-optimize the model\n", - "m.update()\n", - "model.optimize()\u001b[0m\n", + "\u001b[32m# First, update the capacity of supplier1 by halving the current value\n", + "new_capacity_supplier1 = capacity_in_supplier['supplier1'] / 2\n", + "\n", + "# Then, update the supply constraint for supplier1\n", + "model.remove(model.getConstrByName(\"supply_supplier1\"))\n", + "model.addConstr(sum(x[i] for i in shipping_cost_from_supplier_to_roastery.keys() if i[0] == 'supplier1') <= new_capacity_supplier1, \"supply_supplier1\")\n", + "\n", + "# Solve the model again\n", + "model.optimize()\n", + "\n", + "# If the model has an optimal solution, print the new objective value\n", + "if model.status == GRB.OPTIMAL:\n", + " new_objVal = model.objVal\n", + " print(f'New optimal cost with supplier1 halved capacity: {new_objVal}')\n", + "else:\n", + " print(\"Not solved to optimality. Optimization status:\", model.status)\u001b[0m\n", "safeguard (to shield):\n", "\n", "\n", "--- Code ---\n", - "# Update supplier1's capacity to half of the original value\n", - "capacity_in_supplier[\"supplier1\"] = capacity_in_supplier[\"supplier1\"] // 2\n", - "\n", - "# Update the supply constraint for supplier1\n", - "m.remove(m.getConstrByName(\"supply_supplier1\"))\n", - "m.addConstr(\n", - " sum(x[i]\n", - " for i in shipping_cost_from_supplier_to_roastery.keys()\n", - " if i[0] == \"supplier1\") <= capacity_in_supplier[\"supplier1\"],\n", - " \"supply_supplier1\")\n", - "\n", - "# Re-optimize the model\n", - "m.update()\n", + "# First, update the capacity of supplier1 by halving the current value\n", + "new_capacity_supplier1 = capacity_in_supplier['supplier1'] / 2\n", + "\n", + "# Then, update the supply constraint for supplier1\n", + "model.remove(model.getConstrByName(\"supply_supplier1\"))\n", + "model.addConstr(sum(x[i] for i in shipping_cost_from_supplier_to_roastery.keys() if i[0] == 'supplier1') <= new_capacity_supplier1, \"supply_supplier1\")\n", + "\n", + "# Solve the model again\n", "model.optimize()\n", "\n", + "# If the model has an optimal solution, print the new objective value\n", + "if model.status == GRB.OPTIMAL:\n", + " new_objVal = model.objVal\n", + " print(f'New optimal cost with supplier1 halved capacity: {new_objVal}')\n", + "else:\n", + " print(\"Not solved to optimality. Optimization status:\", model.status)\n", + "\n", "--- One-Word Answer: SAFE or DANGER ---\n", "\n", "\n", @@ -566,18 +512,18 @@ "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)\n", "Thread count: 64 physical cores, 128 logical processors, using up to 32 threads\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0x1816ab29\n", + "Model fingerprint: 0xb1e18c0e\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", " Objective range [2e+00, 1e+01]\n", " Bounds range [0e+00, 0e+00]\n", " RHS range [2e+01, 2e+02]\n", - "Found heuristic solution: objective 2900.0000000\n", + "Found heuristic solution: objective 3490.0000000\n", "Presolve time: 0.00s\n", "Presolved: 11 rows, 18 columns, 36 nonzeros\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", - "Found heuristic solution: objective 2896.0000000\n", + "Found heuristic solution: objective 3486.0000000\n", "\n", "Root relaxation: objective 2.470000e+03, 11 iterations, 0.00 seconds (0.00 work units)\n", "\n", @@ -589,14 +535,14 @@ "Explored 1 nodes (11 simplex iterations) in 0.01 seconds (0.00 work units)\n", "Thread count was 32 (of 128 available processors)\n", "\n", - "Solution count 3: 2470 2896 2900 \n", + "Solution count 3: 2470 3486 3490 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)\n", "Thread count: 64 physical cores, 128 logical processors, using up to 32 threads\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0xaed0fb9c\n", + "Model fingerprint: 0xc880842d\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", @@ -625,10 +571,11 @@ "\n", "Model is infeasible\n", "Best objective -, best bound -, gap -\n", + "Not solved to optimality. Optimization status: 3\n", "Gurobi Optimizer version 9.5.1 build v9.5.1rc2 (linux64)\n", "Thread count: 64 physical cores, 128 logical processors, using up to 32 threads\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0xaed0fb9c\n", + "Model fingerprint: 0xc880842d\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", @@ -647,7 +594,7 @@ "\n", "Model is infeasible\n", "Best objective -, best bound -, gap -\n", - "Tue Jul 25 15:25:02 2023\n", + "Tue Jul 25 20:25:47 2023\n", "Not solved to optimality. Optimization status: 3\n", "\u001b[33minfeasible\u001b[0m\n", "coder interpreter (to pencil):\n", @@ -663,16 +610,16 @@ "--------------------------------------------------------------------------------\n", "pencil (to coder interpreter):\n", "\n", - "After reducing supplier1's capacity to half of its original value, the optimization problem became infeasible. This means that with the new capacity constraint, it is not possible to satisfy all the demand for light and dark coffee at the cafes while maintaining the existing constraints.\n", + "After reducing the supply capacity of supplier1 to half of its original value, the optimization problem became infeasible. This indicates that it is not possible to meet the demands for light and dark roasted coffee at all cafes with the new constraint.\n", "\n", - "The original optimal cost for the distribution network was 2470.0. However, with the updated supplier1 capacity constraint, the model can no longer find an optimal solution, indicating that the current network cannot meet the coffee demands under these conditions.\n", + "The original solution, before modifying supplier1's capacity, had an optimal cost of 2470.0. However, with the reduced capacity, the problem cannot be solved, suggesting that supplier1's original capacity is critical for satisfying the demands. It might be necessary to reevaluate or revise the capacities of other suppliers, shipping routes, or even roasting costs to find a feasible solution with the new constraint.\n", "\n", "--------------------------------------------------------------------------------\n", "OptiGuide Coffee Example (to user):\n", "\n", - "After reducing supplier1's capacity to half of its original value, the optimization problem became infeasible. This means that with the new capacity constraint, it is not possible to satisfy all the demand for light and dark coffee at the cafes while maintaining the existing constraints.\n", + "After reducing the supply capacity of supplier1 to half of its original value, the optimization problem became infeasible. This indicates that it is not possible to meet the demands for light and dark roasted coffee at all cafes with the new constraint.\n", "\n", - "The original optimal cost for the distribution network was 2470.0. However, with the updated supplier1 capacity constraint, the model can no longer find an optimal solution, indicating that the current network cannot meet the coffee demands under these conditions.\n", + "The original solution, before modifying supplier1's capacity, had an optimal cost of 2470.0. However, with the reduced capacity, the problem cannot be solved, suggesting that supplier1's original capacity is critical for satisfying the demands. It might be necessary to reevaluate or revise the capacities of other suppliers, shipping routes, or even roasting costs to find a feasible solution with the new constraint.\n", "\n", "--------------------------------------------------------------------------------\n" ] From 2771f05c07930875f4cd80e86f2a66be4afc6529 Mon Sep 17 00:00:00 2001 From: Beibin Li Date: Tue, 25 Jul 2023 21:29:09 -0700 Subject: [PATCH 4/5] Update notebook --- notebook/autogen_optiguide.ipynb | 52 ++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 16 deletions(-) diff --git a/notebook/autogen_optiguide.ipynb b/notebook/autogen_optiguide.ipynb index 684ed6b88c..3f987f972a 100644 --- a/notebook/autogen_optiguide.ipynb +++ b/notebook/autogen_optiguide.ipynb @@ -6,6 +6,7 @@ "metadata": {}, "source": [ "# OptiGuide Example\n", + "\n", "\n" ] }, @@ -25,8 +26,12 @@ "id": "5e92d200", "metadata": {}, "source": [ - "## OptiGuide for Supply Chain Optimization\n", - "The original system design for OptiGuide is \n", + "## OptiGuide for Supply Chain Optimization: System Design Overview\n", + "\n", + "The original system design for OptiGuide, tailored for supply chain optimization, is presented below.\n", + "\n", + "The collaboration among three agents -- Coder, Safeguard, and Interpreter -- lies at the core of this system. They leverage a set of external tools and a large language model (LLM) to address users' questions related to supply chain applications. For a comprehensive understanding of the design and data flow, detailed information can be found in the original [paper](https://arxiv.org/abs/2307.03875).\n", + "\n", "\n", "![optiguide system](https://www.beibinli.com/docs/optiguide/optiguide_system.png)\n" ] @@ -36,22 +41,42 @@ "id": "18c316b5", "metadata": {}, "source": [ - "## OptiGuide in Autogen\n", + "## OptiGuide Integration with Autogen\n", + "\n", + "The OptiGuide framework can be implemented using various large language model (LLM) packages or libraries. Autogen provides a multi-agent design that facilitates agent interactions and memory management, resulting in a more elegant implementation of OptiGuide.\n", + "\n", + "Here, we have broken down and implemented the OptiGuide framework within Autogen, with details as follows.\n", "\n", - "With autogen, we redesign the framework as follow.\n", + "The central role of the OptiGuide agent is to serve as a master agent, overseeing the creation and management of several distinct agents. Additionally, it takes responsibility for handling memory related to user interactions, enabling it to retain valuable information pertaining to users' questions and their corresponding answers. This memory is then shared with other agents within the system, allowing them to effectively consider a user's previous inquiries.\n", + "\n", + "The OptiGuide agent supervises three user proxy agents and two assistant agents, each with a specific function:\n", + "- Coder: A user proxy agent dedicated to the task of writing code.\n", + "- Safeguard: Another user proxy agent designed to identify and detect adversarial code effectively.\n", + "- Interpreter: A user proxy agent specialized in interpreting execution results and presenting them in a human-readable format.\n", + "- Pencil: An assistant agent providing support to both the Coder and Interpreter. It possesses the capability to \"write\" code and provide interpretations.\n", + "- Shield: An assistant agent offering support to the Safeguard.\n", + "\n", + "In this implementation, the \"Coder\" and \"Interpreter\" refer to the same entity utilizing the Pencil to generate content, which could either be code or English sentences. This design choice enables the Pencil to access and share the same memory while engaging in communication with both the \"Coder\" and the \"Interpreter.\"\n", + "\n", + "Additionally, it is worth noting that the Safeguard does not possess the same memory as the Coder or Interpreter. This deliberate decision ensures that the Safeguard can impartially act as an adversarial checker. By preventing the exposure of LLM responses of code and interpretations to the Safeguard, potential biases are mitigated, and more reliable results can be achieved.\n", "\n", - "Note that we have\n", "\n", "![optiguide system](https://www.beibinli.com/docs/optiguide/autogen_optiguide_sys.png)" ] }, { - "cell_type": "code", - "execution_count": null, - "id": "161ec2a5", + "cell_type": "markdown", + "id": "8b7f90c8", "metadata": {}, - "outputs": [], - "source": [] + "source": [ + "Advantages of this multi-agent design with autogen:\n", + "- Collaborative Problem Solving: The collaboration among the user proxy agents (Coder and Interpreter) and the assistant agents (Pencil and Shield) fosters a cooperative problem-solving environment. The agents can share information and knowledge, allowing them to complement each other's abilities and collectively arrive at better solutions. On the other hand, the Safeguard acts as a virtual adversarial checker, which can perform another safety check pass on the generated code.\n", + "\n", + "- Modularity: The division of tasks into separate agents promotes modularity in the system. Each agent can be developed, tested, and maintained independently, simplifying the overall development process and facilitating code management.\n", + "\n", + "- Memory Management: The OptiGuide agent's role in maintaining memory related to user interactions is crucial. The memory retention allows the agents to have context about a user's prior questions, making the decision-making process more informed and context-aware.\n", + "\n" + ] }, { "cell_type": "code", @@ -124,12 +149,7 @@ "openai.api_type = \"azure\"\n", "openai.api_version = \"2023-03-15-preview\"\n", "openai.api_base = \"https://your.api.address.here.com\"\n", - "openai.api_key = \"\"\n", - "\n", - "openai.api_type = \"azure\"\n", - "openai.api_base = \"https://msrcore.openai.azure.com\"\n", - "openai.api_version = \"2023-03-15-preview\"\n", - "openai.api_key = os.getenv(\"CORE_AZURE_KEY\")" + "openai.api_key = \"\"" ] }, { From 438f2116aa89adab9a4c237a020913e3c1ad6d04 Mon Sep 17 00:00:00 2001 From: Beibin Li Date: Wed, 26 Jul 2023 09:50:30 -0700 Subject: [PATCH 5/5] Rename: GenericAgent -> ResponsiveAgent --- flaml/autogen/agent/__init__.py | 6 +++--- flaml/autogen/agent/opti_guide.py | 4 ++-- notebook/autogen_optiguide.ipynb | 4 ++-- setup.py | 1 + 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/flaml/autogen/agent/__init__.py b/flaml/autogen/agent/__init__.py index 694f35cc5a..bb051b5752 100644 --- a/flaml/autogen/agent/__init__.py +++ b/flaml/autogen/agent/__init__.py @@ -1,7 +1,7 @@ from .agent import Agent -from .responsive_agent import ResponsiveAgent from .assistant_agent import AssistantAgent +from .opti_guide import OptiGuideAgent +from .responsive_agent import ResponsiveAgent from .user_proxy_agent import UserProxyAgent - -__all__ = ["Agent", "ResponsiveAgent", "AssistantAgent", "UserProxyAgent"] +__all__ = ["Agent", "ResponsiveAgent", "AssistantAgent", "UserProxyAgent", "OptiGuideAgent"] diff --git a/flaml/autogen/agent/opti_guide.py b/flaml/autogen/agent/opti_guide.py index 28bd867182..81f39defa0 100644 --- a/flaml/autogen/agent/opti_guide.py +++ b/flaml/autogen/agent/opti_guide.py @@ -28,7 +28,7 @@ from .agent import Agent from .assistant_agent import AssistantAgent -from .generic_agent import GenericAgent +from .responsive_agent import ResponsiveAgent from .user_proxy_agent import UserProxyAgent # %% System Messages @@ -78,7 +78,7 @@ # %% -class OptiGuideAgent(GenericAgent): +class OptiGuideAgent(ResponsiveAgent): """(Experimental) OptiGuide is an agent to write Python code and to answer users questions for supply chain-related coding project. diff --git a/notebook/autogen_optiguide.ipynb b/notebook/autogen_optiguide.ipynb index 3f987f972a..0535629e4e 100644 --- a/notebook/autogen_optiguide.ipynb +++ b/notebook/autogen_optiguide.ipynb @@ -123,7 +123,7 @@ "\n", "# import flaml and autogen\n", "from flaml import oai\n", - "from flaml.autogen.agent import Agent, GenericAgent\n", + "from flaml.autogen.agent import Agent, ResponsiveAgent\n", "from flaml.autogen.agent.opti_guide import OptiGuideAgent" ] }, @@ -272,7 +272,7 @@ " source_code=code,\n", " example_qa=\"\")\n", "\n", - "user = GenericAgent(\"user\", max_consecutive_auto_reply=0,\n", + "user = ResponsiveAgent(\"user\", max_consecutive_auto_reply=0,\n", " human_input_mode=\"NEVER\", code_execution_config=False)\n" ] }, diff --git a/setup.py b/setup.py index 63fc978180..b7ddbc0c23 100644 --- a/setup.py +++ b/setup.py @@ -148,6 +148,7 @@ "joblib<1.3.0", # temp solution for joblib 1.3.0 issue, no need once https://github.com/joblib/joblib-spark/pull/48 is merged ], "autozero": ["scikit-learn", "pandas", "packaging"], + "optiguide": ["gurobipy", "eventlet", "termcolor"], }, classifiers=[ "Programming Language :: Python :: 3",