From a13fba7a9100d82acaec96905de0d90eced22829 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Wed, 7 Feb 2024 13:36:28 -0500 Subject: [PATCH 01/57] add util --- autogen/agent_utils.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/autogen/agent_utils.py b/autogen/agent_utils.py index 357112374f7..dde90989f74 100644 --- a/autogen/agent_utils.py +++ b/autogen/agent_utils.py @@ -1,4 +1,12 @@ from typing import List, Dict, Tuple +from functools import partial + + +def nested_chat_reply(nested_agent, **context): + def chat(nested_agent, recipient, messages, sender, config): + return True, recipient.initiate_chat(nested_agent, message=messages[0], **context) + + return partial(chat, nested_agent) def gather_usage_summary(agents: List) -> Tuple[Dict[str, any], Dict[str, any]]: From 8fb3dd775035f8ffc7b2e3d537845b00d884d151 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Thu, 8 Feb 2024 22:24:10 -0500 Subject: [PATCH 02/57] add notebook --- ...chat_nestedchat_Society_of_Mind copy.ipynb | 1078 ++++++++++++++ notebook/agentchat_nestedchat_chess.ipynb | 944 ++++++++++++ notebook/agentchat_nestedchat_optiguide.ipynb | 1307 +++++++++++++++++ 3 files changed, 3329 insertions(+) create mode 100644 notebook/agentchat_nestedchat_Society_of_Mind copy.ipynb create mode 100644 notebook/agentchat_nestedchat_chess.ipynb create mode 100644 notebook/agentchat_nestedchat_optiguide.ipynb diff --git a/notebook/agentchat_nestedchat_Society_of_Mind copy.ipynb b/notebook/agentchat_nestedchat_Society_of_Mind copy.ipynb new file mode 100644 index 00000000000..9ae79d575de --- /dev/null +++ b/notebook/agentchat_nestedchat_Society_of_Mind copy.ipynb @@ -0,0 +1,1078 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Auto Generated Agent Chat: Society of Minds\n", + "\n", + "This notebook showcases how to use conversable agents in AutoGen to realize a Society of Mind (SoM) scenario, in which an agent runs a chat session with another agent as an inner-monologue. The idea of SoM and part of the example demonstrated in this notebook come from this PR: https://github.com/microsoft/autogen/pull/890\n", + " \n", + "\n", + "## Requirements\n", + "\n", + "AutoGen requires `Python>=3.8`. To run this notebook example, please install:\n", + "```bash\n", + "pip install pyautogen\n", + "```" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Society of Minds" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import autogen\n", + "\n", + "config_list = autogen.config_list_from_json(env_or_file=\"OAI_CONFIG_LIST\")\n", + "llm_config = {\"config_list\": config_list, \"cache_seed\": 44}\n", + "\n", + "financial_tasks = [\n", + " \"\"\"On which days this year was Microsoft Stock higher than $400? Put results in a table.\"\"\",\n", + " \"\"\"Investigate the possible reasons of the stock performance.\"\"\",\n", + "]\n", + "\n", + "writing_tasks = [\"\"\"Develop an engaging blog post using any information provided.\"\"\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Construct a group chat manager to be used for the inner-monologue" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "assistant = autogen.AssistantAgent(\n", + " \"Inner-assistant\",\n", + " llm_config=llm_config,\n", + " is_termination_msg=lambda x: x.get(\"content\", \"\").find(\"TERMINATE\") >= 0,\n", + ")\n", + "\n", + "code_interpreter = autogen.UserProxyAgent(\n", + " \"Inner-code-interpreter\",\n", + " human_input_mode=\"NEVER\",\n", + " code_execution_config={\n", + " \"work_dir\": \"coding\",\n", + " \"use_docker\": False,\n", + " },\n", + " default_auto_reply=\"\",\n", + " is_termination_msg=lambda x: x.get(\"content\", \"\").find(\"TERMINATE\") >= 0,\n", + ")\n", + "\n", + "groupchat = autogen.GroupChat(\n", + " agents=[assistant, code_interpreter],\n", + " messages=[],\n", + " speaker_selection_method=\"round_robin\", # With two agents, this is equivalent to a 1:1 conversation.\n", + " allow_repeat_speaker=False,\n", + " max_round=8,\n", + ")\n", + "\n", + "manager = autogen.GroupChatManager(\n", + " groupchat=groupchat,\n", + " is_termination_msg=lambda x: x.get(\"content\", \"\").find(\"TERMINATE\") >= 0,\n", + " llm_config=llm_config,\n", + " code_execution_config={\n", + " \"work_dir\": \"coding\",\n", + " \"use_docker\": False,\n", + " },\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example 1: Solve one task with SoM" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mUser\u001b[0m (to Financial_assistant_1):\n", + "\n", + "On which days this year was Microsoft Stock higher than $400? Put results in a table.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "On which days this year was Microsoft Stock higher than $400? Put results in a table.\n", + "\n", + "With the following carryover: \n", + "\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mFinancial_assistant_1\u001b[0m (to chat_manager):\n", + "\n", + "On which days this year was Microsoft Stock higher than $400? Put results in a table.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", + "\n", + "To determine on which days Microsoft (ticker symbol: MSFT) stock was trading higher than $400 this year, I will use Python with the `pandas` library and the `yfinance` package to fetch historical stock price data from Yahoo Finance. To get this data, you must first ensure that `pandas` and `yfinance` are installed in your Python environment. If they're not installed, you can install them using pip.\n", + "\n", + "Here's a step-by-step plan we're going to follow:\n", + "\n", + "1. Import the required libraries.\n", + "2. Set up the ticker symbol for Microsoft.\n", + "3. Define the date range for \"this year\" until today.\n", + "4. Use yfinance to download the stock data for Microsoft.\n", + "5. Filter the data to find the days when the stock was higher than $400.\n", + "6. Print out the result in a tabular format.\n", + "\n", + "Please run the following code:\n", + "\n", + "```python\n", + "# filename: msft_stock_days_above_400.py\n", + "import pandas as pd\n", + "import yfinance as yf\n", + "from datetime import datetime\n", + "\n", + "# Ensure that pandas and yfinance are installed\n", + "try:\n", + " import pandas\n", + " import yfinance\n", + "except ImportError as e:\n", + " print(\"Please install pandas and yfinance using 'pip install pandas yfinance'\")\n", + " raise e\n", + "\n", + "def find_msft_days_above_400():\n", + " # Ticker symbol for Microsoft\n", + " ticker_symbol = \"MSFT\"\n", + " \n", + " # get the current year\n", + " current_year = datetime.now().year\n", + "\n", + " # Define the date range for this year until today\n", + " start_date = f\"{current_year}-01-01\"\n", + " end_date = datetime.now().strftime('%Y-%m-%d')\n", + "\n", + " # Download the stock price data\n", + " msft_data = yf.download(ticker_symbol, start=start_date, end=end_date)\n", + "\n", + " # Filter the data for days when the closing price was higher than $400\n", + " days_above_400 = msft_data[msft_data['Close'] > 400]\n", + "\n", + " # Check if there are any days that the closing price was above $400\n", + " if not days_above_400.empty:\n", + " # Print the result in a tabular format\n", + " print(days_above_400[['Close']].to_markdown())\n", + " else:\n", + " print(f\"No days in {current_year} where the Microsoft stock closed above $400.\")\n", + "\n", + "if __name__ == \"__main__\":\n", + " find_msft_days_above_400()\n", + "```\n", + "\n", + "Please save the above code to a file and run it using Python. It will print the days when Microsoft stock was higher than $400 this year, if any, in a markdown table format. If the stock has not surpassed that value this year, the script will tell you so.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n", + "\u001b[33mInner-code-interpreter\u001b[0m (to chat_manager):\n", + "\n", + "exitcode: 0 (execution succeeded)\n", + "Code output: \n", + "| Date | Close |\n", + "|:--------------------|--------:|\n", + "| 2024-01-24 00:00:00 | 402.56 |\n", + "| 2024-01-25 00:00:00 | 404.87 |\n", + "| 2024-01-26 00:00:00 | 403.93 |\n", + "| 2024-01-29 00:00:00 | 409.72 |\n", + "| 2024-01-30 00:00:00 | 408.59 |\n", + "| 2024-02-01 00:00:00 | 403.78 |\n", + "| 2024-02-02 00:00:00 | 411.22 |\n", + "| 2024-02-05 00:00:00 | 405.65 |\n", + "| 2024-02-06 00:00:00 | 405.49 |\n", + "| 2024-02-07 00:00:00 | 414.05 |\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", + "\n", + "Great, the Python script successfully retrieved the data! Here's the table with the results:\n", + "\n", + "| Date | Close |\n", + "|:--------------------|--------:|\n", + "| 2024-01-24 00:00:00 | 402.56 |\n", + "| 2024-01-25 00:00:00 | 404.87 |\n", + "| 2024-01-26 00:00:00 | 403.93 |\n", + "| 2024-01-29 00:00:00 | 409.72 |\n", + "| 2024-01-30 00:00:00 | 408.59 |\n", + "| 2024-02-01 00:00:00 | 403.78 |\n", + "| 2024-02-02 00:00:00 | 411.22 |\n", + "| 2024-02-05 00:00:00 | 405.65 |\n", + "| 2024-02-06 00:00:00 | 405.49 |\n", + "| 2024-02-07 00:00:00 | 414.05 |\n", + "\n", + "These are the dates in 2024 when Microsoft's stock closed above $400. If you need more detailed information or further assistance, please let me know!\n", + "\n", + "TERMINATE\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mFinancial_assistant_1\u001b[0m (to User):\n", + "\n", + "Microsoft's stock closed above $400 on the following dates in 2024: January 24th ($402.56), January 25th ($404.87), January 26th ($403.93), January 29th ($409.72), January 30th ($408.59), February 1st ($403.78), February 2nd ($411.22), February 5th ($405.65), February 6th ($405.49), and February 7th ($414.05).\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mUser\u001b[0m (to Financial_assistant_1):\n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "data": { + "text/plain": [ + "ChatResult(chat_history=[{'content': 'On which days this year was Microsoft Stock higher than $400? Put results in a table.', 'role': 'assistant'}, {'content': \"Microsoft's stock closed above $400 on the following dates in 2024: January 24th ($402.56), January 25th ($404.87), January 26th ($403.93), January 29th ($409.72), January 30th ($408.59), February 1st ($403.78), February 2nd ($411.22), February 5th ($405.65), February 6th ($405.49), and February 7th ($414.05).\", 'role': 'user'}, {'content': '', 'role': 'assistant'}], summary='', cost=({'total_cost': 0}, {'total_cost': 0}), human_input=[])" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "financial_assistant_1 = autogen.AssistantAgent(\n", + " name=\"Financial_assistant_1\",\n", + " llm_config={\"config_list\": config_list},\n", + " # is_termination_msg=lambda x: x.get(\"content\", \"\") == \"\",\n", + ")\n", + "\n", + "financial_assistant_2 = autogen.AssistantAgent(\n", + " name=\"Financial_assistant_2\",\n", + " llm_config={\"config_list\": config_list},\n", + " # is_termination_msg=lambda x: x.get(\"content\", \"\") == \"\",\n", + ")\n", + "\n", + "user = autogen.UserProxyAgent(\n", + " name=\"User\",\n", + " human_input_mode=\"NEVER\",\n", + " is_termination_msg=lambda x: x.get(\"content\", \"\").find(\"TERMINATE\") >= 0,\n", + " code_execution_config={\n", + " \"last_n_messages\": 1,\n", + " \"work_dir\": \"tasks\",\n", + " \"use_docker\": False,\n", + " }, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.\n", + ")\n", + "\n", + "financial_assistant_1.register_nested_chats(\n", + " [autogen.Agent, None], [{\"recipient\": manager, \"summary_method\": \"reflection_with_llm\"}]\n", + ")\n", + "user.initiate_chat(financial_assistant_1, message=financial_tasks[0])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example 2: Solve multiple tasks with SoM" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "On which days this year was Microsoft Stock higher than $400? Put results in a table.\n", + "\n", + "With the following carryover: \n", + "\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mUser\u001b[0m (to Financial_assistant_1):\n", + "\n", + "On which days this year was Microsoft Stock higher than $400? Put results in a table.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "On which days this year was Microsoft Stock higher than $400? Put results in a table.\n", + "\n", + "With the following carryover: \n", + "\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mFinancial_assistant_1\u001b[0m (to chat_manager):\n", + "\n", + "On which days this year was Microsoft Stock higher than $400? Put results in a table.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", + "\n", + "To determine on which days Microsoft (ticker symbol: MSFT) stock was trading higher than $400 this year, I will use Python with the `pandas` library and the `yfinance` package to fetch historical stock price data from Yahoo Finance. To get this data, you must first ensure that `pandas` and `yfinance` are installed in your Python environment. If they're not installed, you can install them using pip.\n", + "\n", + "Here's a step-by-step plan we're going to follow:\n", + "\n", + "1. Import the required libraries.\n", + "2. Set up the ticker symbol for Microsoft.\n", + "3. Define the date range for \"this year\" until today.\n", + "4. Use yfinance to download the stock data for Microsoft.\n", + "5. Filter the data to find the days when the stock was higher than $400.\n", + "6. Print out the result in a tabular format.\n", + "\n", + "Please run the following code:\n", + "\n", + "```python\n", + "# filename: msft_stock_days_above_400.py\n", + "import pandas as pd\n", + "import yfinance as yf\n", + "from datetime import datetime\n", + "\n", + "# Ensure that pandas and yfinance are installed\n", + "try:\n", + " import pandas\n", + " import yfinance\n", + "except ImportError as e:\n", + " print(\"Please install pandas and yfinance using 'pip install pandas yfinance'\")\n", + " raise e\n", + "\n", + "def find_msft_days_above_400():\n", + " # Ticker symbol for Microsoft\n", + " ticker_symbol = \"MSFT\"\n", + " \n", + " # get the current year\n", + " current_year = datetime.now().year\n", + "\n", + " # Define the date range for this year until today\n", + " start_date = f\"{current_year}-01-01\"\n", + " end_date = datetime.now().strftime('%Y-%m-%d')\n", + "\n", + " # Download the stock price data\n", + " msft_data = yf.download(ticker_symbol, start=start_date, end=end_date)\n", + "\n", + " # Filter the data for days when the closing price was higher than $400\n", + " days_above_400 = msft_data[msft_data['Close'] > 400]\n", + "\n", + " # Check if there are any days that the closing price was above $400\n", + " if not days_above_400.empty:\n", + " # Print the result in a tabular format\n", + " print(days_above_400[['Close']].to_markdown())\n", + " else:\n", + " print(f\"No days in {current_year} where the Microsoft stock closed above $400.\")\n", + "\n", + "if __name__ == \"__main__\":\n", + " find_msft_days_above_400()\n", + "```\n", + "\n", + "Please save the above code to a file and run it using Python. It will print the days when Microsoft stock was higher than $400 this year, if any, in a markdown table format. If the stock has not surpassed that value this year, the script will tell you so.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n", + "\u001b[33mInner-code-interpreter\u001b[0m (to chat_manager):\n", + "\n", + "exitcode: 0 (execution succeeded)\n", + "Code output: \n", + "| Date | Close |\n", + "|:--------------------|--------:|\n", + "| 2024-01-24 00:00:00 | 402.56 |\n", + "| 2024-01-25 00:00:00 | 404.87 |\n", + "| 2024-01-26 00:00:00 | 403.93 |\n", + "| 2024-01-29 00:00:00 | 409.72 |\n", + "| 2024-01-30 00:00:00 | 408.59 |\n", + "| 2024-02-01 00:00:00 | 403.78 |\n", + "| 2024-02-02 00:00:00 | 411.22 |\n", + "| 2024-02-05 00:00:00 | 405.65 |\n", + "| 2024-02-06 00:00:00 | 405.49 |\n", + "| 2024-02-07 00:00:00 | 414.05 |\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", + "\n", + "Great, the Python script successfully retrieved the data! Here's the table with the results:\n", + "\n", + "| Date | Close |\n", + "|:--------------------|--------:|\n", + "| 2024-01-24 00:00:00 | 402.56 |\n", + "| 2024-01-25 00:00:00 | 404.87 |\n", + "| 2024-01-26 00:00:00 | 403.93 |\n", + "| 2024-01-29 00:00:00 | 409.72 |\n", + "| 2024-01-30 00:00:00 | 408.59 |\n", + "| 2024-02-01 00:00:00 | 403.78 |\n", + "| 2024-02-02 00:00:00 | 411.22 |\n", + "| 2024-02-05 00:00:00 | 405.65 |\n", + "| 2024-02-06 00:00:00 | 405.49 |\n", + "| 2024-02-07 00:00:00 | 414.05 |\n", + "\n", + "These are the dates in 2024 when Microsoft's stock closed above $400. If you need more detailed information or further assistance, please let me know!\n", + "\n", + "TERMINATE\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mFinancial_assistant_1\u001b[0m (to User):\n", + "\n", + "Microsoft's stock closed above $400 on the following dates in 2024: January 24th ($402.56), January 25th ($404.87), January 26th ($403.93), January 29th ($409.72), January 30th ($408.59), February 1st ($403.78), February 2nd ($411.22), February 5th ($405.65), February 6th ($405.49), and February 7th ($414.05).\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mUser\u001b[0m (to Financial_assistant_1):\n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "Investigate the possible reasons of the stock performance.\n", + "\n", + "With the following carryover: \n", + "\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mUser\u001b[0m (to Financial_assistant_2):\n", + "\n", + "Investigate the possible reasons of the stock performance.\n", + "Context: \n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "Investigate the possible reasons of the stock performance.\n", + "Context: \n", + "\n", + "\n", + "With the following carryover: \n", + "\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mFinancial_assistant_2\u001b[0m (to chat_manager):\n", + "\n", + "Investigate the possible reasons of the stock performance.\n", + "Context: \n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", + "\n", + "To investigate the possible reasons for a stock's performance, we need to collect and analyze a variety of data. This could include the company's financial statements, recent news, market trends, sector performance, and analyst ratings. Let's break this down into a step-by-step plan:\n", + "\n", + "1. Identify the specific stock that you want to investigate.\n", + "2. Collect the most recent financial statements of the company to analyze its financial health.\n", + "3. Search for recent news articles and press releases related to the company, which may include information on earnings reports, product launches, partnerships, or any corporate actions that could affect the stock performance.\n", + "4. Look at overall market trends to see how they may be impacting the stock. This includes broader economic indicators and market sentiment that can influence investor decisions.\n", + "5. Compare the stock's performance with its sector or industry to determine if the movement is company-specific or part of a larger trend.\n", + "6. Check analyst ratings and forecasts for the stock, as these can influence investor expectations and sentiment.\n", + "\n", + "Since you're asking for financial assistance related to stock performance, I assume you're looking for factual data and analysis. As my capabilities do not extend to accessing real-time financial databases or proprietary news feeds, let's start by identifying the stock symbol and looking up recent news and market data from freely available online sources.\n", + "\n", + "If you provide the stock symbol, we can proceed with generating Python code that will fetch recent news headlines and financial data using available APIs or web scraping, if the data source permits it. This would be the code step. After that, I could use my language skills to help you interpret the gathered data.\n", + "\n", + "Please provide the stock symbol or company name you would like to investigate, and confirm if you're comfortable with proceeding in this way.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mInner-code-interpreter\u001b[0m (to chat_manager):\n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", + "\n", + "It looks like \"Inner-code-interpreter\" does not refer to a valid stock symbol. A stock symbol is usually 1 to 5 letters and represents a public company's stock that is traded on an exchange. It seems there might be confusion about the term \"Inner-code-interpreter,\" and it's possible you meant something else.\n", + "\n", + "Please provide the correct stock symbol or clarify your request. Once I have the correct stock symbol, I can help you create a plan on how to proceed with investigating the stock's performance by fetching the relevant data for analysis.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mInner-code-interpreter\u001b[0m (to chat_manager):\n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", + "\n", + "I apologize for any confusion, but it appears that there might be a miscommunication. If \"Inner-code-interpreter\" is intended to be a directive or a stock symbol, it does not match any known stock ticker symbols or standard commands within the context of stock market research.\n", + "\n", + "To provide you with the assistance you are seeking, I would need the ticker symbol of the stock you want to investigate. The ticker symbol is an abbreviation used to uniquely identify publicly traded shares of a particular stock on a particular stock market.\n", + "\n", + "For example, to investigate the performance of Apple Inc., you would provide its ticker symbol AAPL.\n", + "\n", + "If by \"Inner-code-interpreter\" you are referring to the functionality of this text-based interface or to my role as an AI assistant designed to execute code internally, then please provide guidance on the actual task you would like me to perform, which in this case would be the stock symbol or company name you seek to investigate.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mInner-code-interpreter\u001b[0m (to chat_manager):\n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", + "\n", + "It seems you are referring to the functionality of this interface or to my capability as an AI to interpret and execute code. Since this is not directly related to investigating the performance of a stock, I need you to either provide a valid stock symbol for analysis or clarify the task at hand.\n", + "\n", + "If your intention is to analyze the performance of a specific stock, please provide the ticker symbol or company name, and I can assist with that process.\n", + "\n", + "If \"Inner-code-interpreter\" is meant as an instruction or a placeholder for another task, please provide the actual details of the task or instruction you need assistance with.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mFinancial_assistant_2\u001b[0m (to User):\n", + "\n", + "There has been a miscommunication regarding the term \"Inner-code-interpreter,\" which does not match any known stock ticker symbols or standard commands related to financial assistance or stock performance investigation. To proceed with the analysis of stock performance, the correct stock symbol or company name is required. The user has not provided this information, and thus the financial analysis of a specific stock has not been initiated. Instead, an explanation of how the user can proceed with the analysis upon providing the necessary information has been offered.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mUser\u001b[0m (to Financial_assistant_2):\n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "Develop an engaging blog post using any information provided.\n", + "\n", + "With the following carryover: \n", + "\n", + "\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mUser\u001b[0m (to Writer):\n", + "\n", + "Develop an engaging blog post using any information provided.\n", + "Context: \n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mWriter\u001b[0m (to User):\n", + "\n", + "I'm happy to create an engaging blog post for you, however, it seems there's been an oversight, and no information or context was provided for the content you're looking for. Could you please provide some details or a topic you're interested in? This could relate to anything from technology, lifestyle, culture, science, personal development, travel, health, and more. Once I have that, I'll craft a compelling narrative for your intended audience.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mUser\u001b[0m (to Writer):\n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mWriter\u001b[0m (to User):\n", + "\n", + "It appears that your request may have been inadvertently left blank, thus not providing me with a specific theme or topic for developing a blog post. In order to assist you effectively, I need some sort of context or area of interest to focus on.\n", + "\n", + "You may want to consider topics such as emerging technology trends, the impact of social media on communication, the importance of sustainable living practices, or the benefits of mindfulness in today's fast-paced world, among others.\n", + "\n", + "Please provide me with the direction you'd like to take or any specific points you wish to address in your blog post. Once I have this information, I can proceed to craft an engaging and insightful narrative for your audience.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mUser\u001b[0m (to Writer):\n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mWriter\u001b[0m (to User):\n", + "\n", + "It seems like there might be a communication issue since I haven't received any additional details or context for the blog post. In the spirit of creativity and to provide you with a sample of my work, I will proceed to write an engaging piece on a topic that has universal relevance: \"The Power of Innovation in Solving Global Challenges.\"\n", + "\n", + "---\n", + "\n", + "**Igniting Change: The Power of Innovation in Solving Global Challenges**\n", + "\n", + "In a world that's smaller and more connected than ever, human ingenuity has emerged as the most potent tool in our arsenal as we confront a host of global challenges. Innovation, the child of necessity and imagination, has the transformative potential to not only reshape our daily lives but also to propel us towards a more sustainable and equitable future.\n", + "\n", + "From the desalination plants that convert seawater into life-sustaining fresh water to the drones that seed billions of trees in deforested regions, every revolutionary idea began as a mere spark in the human mind. It is this spark that has the power to cross borders, connect cultures, and confront crises—from climate change to global health concerns.\n", + "\n", + "Take the example of renewable energy; once a niche and expensive proposition, it now stands at the forefront of our fight against climate change. Innovations in solar and wind power technology have led to significant cost reductions, making them more accessible and feasible as a primary energy source. This shift not only promises cleaner air and a healthier planet but also jobs, economic growth, and a step towards energy independence for nations.\n", + "\n", + "Amidst a global health pandemic, we've witnessed an unprecedented acceleration in vaccine development. This leap forward was rooted in innovative technologies such as mRNA, which have changed the landscape of medicine and offer promising platforms for tackling infectious diseases that have plagued humanity for centuries.\n", + "\n", + "However, innovation doesn't solely belong to the grandiose scientific breakthroughs that make headlines. It thrives in the quiet corners of daily life, in the processes improved by a factory worker's suggestion, or in the homegrown solutions to local problems that spread globally—like microcredit systems that began in South Asia and are now employed worldwide to combat poverty.\n", + "\n", + "What all these examples highlight is that innovation serves as the link between recognizing a problem and forging a solution. It is not purely about technological advancements, but rather a mindset of approaching challenges with a can-do spirit, willingness to take risks, and a commitment to continuous improvement.\n", + "\n", + "It is incumbent upon us as global citizens, policymakers, entrepreneurs, and educators to foster an environment that encourages creativity and rewards experimentation. By investing in education, promoting cross-disciplinary collaboration, and ensuring equitable access to resources, we can ensure that the fires of innovation remain ablaze.\n", + "\n", + "It is through this collective commitment to cultivating and harnessing the power of innovation that we stand our best chance of solving the most pressing issues of our time. The road ahead is paved with uncertainty, but with the torch of innovation in hand, we can light the way to a brighter, more resilient tomorrow.\n", + "\n", + "---\n", + "\n", + "This post touches on a ubiquitous theme—innovation—and its essential role in addressing the world’s most pressing challenges. I hope this sample aligns with your interests or inspires a specific direction for your blog post. If you provide me with more information on the desired context, I can further tailor the content to meet your needs.\n", + "\n", + "TERMINATE\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "data": { + "text/plain": [ + "{: ChatResult(chat_history=[{'content': 'On which days this year was Microsoft Stock higher than $400? Put results in a table.', 'role': 'assistant'}, {'content': \"Microsoft's stock closed above $400 on the following dates in 2024: January 24th ($402.56), January 25th ($404.87), January 26th ($403.93), January 29th ($409.72), January 30th ($408.59), February 1st ($403.78), February 2nd ($411.22), February 5th ($405.65), February 6th ($405.49), and February 7th ($414.05).\", 'role': 'user'}, {'content': '', 'role': 'assistant'}], summary='', cost=({'total_cost': 0}, {'total_cost': 0}), human_input=[]),\n", + " : ChatResult(chat_history=[{'content': 'Investigate the possible reasons of the stock performance.\\nContext: \\n', 'role': 'assistant'}, {'content': 'There has been a miscommunication regarding the term \"Inner-code-interpreter,\" which does not match any known stock ticker symbols or standard commands related to financial assistance or stock performance investigation. To proceed with the analysis of stock performance, the correct stock symbol or company name is required. The user has not provided this information, and thus the financial analysis of a specific stock has not been initiated. Instead, an explanation of how the user can proceed with the analysis upon providing the necessary information has been offered.', 'role': 'user'}, {'content': '', 'role': 'assistant'}], summary='', cost=({'total_cost': 0}, {'total_cost': 0}), human_input=[]),\n", + " : ChatResult(chat_history=[{'content': 'Develop an engaging blog post using any information provided.\\nContext: \\n\\n', 'role': 'assistant'}, {'content': \"I'm happy to create an engaging blog post for you, however, it seems there's been an oversight, and no information or context was provided for the content you're looking for. Could you please provide some details or a topic you're interested in? This could relate to anything from technology, lifestyle, culture, science, personal development, travel, health, and more. Once I have that, I'll craft a compelling narrative for your intended audience.\", 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': \"It appears that your request may have been inadvertently left blank, thus not providing me with a specific theme or topic for developing a blog post. In order to assist you effectively, I need some sort of context or area of interest to focus on.\\n\\nYou may want to consider topics such as emerging technology trends, the impact of social media on communication, the importance of sustainable living practices, or the benefits of mindfulness in today's fast-paced world, among others.\\n\\nPlease provide me with the direction you'd like to take or any specific points you wish to address in your blog post. Once I have this information, I can proceed to craft an engaging and insightful narrative for your audience.\", 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': 'It seems like there might be a communication issue since I haven\\'t received any additional details or context for the blog post. In the spirit of creativity and to provide you with a sample of my work, I will proceed to write an engaging piece on a topic that has universal relevance: \"The Power of Innovation in Solving Global Challenges.\"\\n\\n---\\n\\n**Igniting Change: The Power of Innovation in Solving Global Challenges**\\n\\nIn a world that\\'s smaller and more connected than ever, human ingenuity has emerged as the most potent tool in our arsenal as we confront a host of global challenges. Innovation, the child of necessity and imagination, has the transformative potential to not only reshape our daily lives but also to propel us towards a more sustainable and equitable future.\\n\\nFrom the desalination plants that convert seawater into life-sustaining fresh water to the drones that seed billions of trees in deforested regions, every revolutionary idea began as a mere spark in the human mind. It is this spark that has the power to cross borders, connect cultures, and confront crises—from climate change to global health concerns.\\n\\nTake the example of renewable energy; once a niche and expensive proposition, it now stands at the forefront of our fight against climate change. Innovations in solar and wind power technology have led to significant cost reductions, making them more accessible and feasible as a primary energy source. This shift not only promises cleaner air and a healthier planet but also jobs, economic growth, and a step towards energy independence for nations.\\n\\nAmidst a global health pandemic, we\\'ve witnessed an unprecedented acceleration in vaccine development. This leap forward was rooted in innovative technologies such as mRNA, which have changed the landscape of medicine and offer promising platforms for tackling infectious diseases that have plagued humanity for centuries.\\n\\nHowever, innovation doesn\\'t solely belong to the grandiose scientific breakthroughs that make headlines. It thrives in the quiet corners of daily life, in the processes improved by a factory worker\\'s suggestion, or in the homegrown solutions to local problems that spread globally—like microcredit systems that began in South Asia and are now employed worldwide to combat poverty.\\n\\nWhat all these examples highlight is that innovation serves as the link between recognizing a problem and forging a solution. It is not purely about technological advancements, but rather a mindset of approaching challenges with a can-do spirit, willingness to take risks, and a commitment to continuous improvement.\\n\\nIt is incumbent upon us as global citizens, policymakers, entrepreneurs, and educators to foster an environment that encourages creativity and rewards experimentation. By investing in education, promoting cross-disciplinary collaboration, and ensuring equitable access to resources, we can ensure that the fires of innovation remain ablaze.\\n\\nIt is through this collective commitment to cultivating and harnessing the power of innovation that we stand our best chance of solving the most pressing issues of our time. The road ahead is paved with uncertainty, but with the torch of innovation in hand, we can light the way to a brighter, more resilient tomorrow.\\n\\n---\\n\\nThis post touches on a ubiquitous theme—innovation—and its essential role in addressing the world’s most pressing challenges. I hope this sample aligns with your interests or inspires a specific direction for your blog post. If you provide me with more information on the desired context, I can further tailor the content to meet your needs.\\n\\nTERMINATE', 'role': 'user'}], summary='It seems like there might be a communication issue since I haven\\'t received any additional details or context for the blog post. In the spirit of creativity and to provide you with a sample of my work, I will proceed to write an engaging piece on a topic that has universal relevance: \"The Power of Innovation in Solving Global Challenges.\"\\n\\n---\\n\\n**Igniting Change: The Power of Innovation in Solving Global Challenges**\\n\\nIn a world that\\'s smaller and more connected than ever, human ingenuity has emerged as the most potent tool in our arsenal as we confront a host of global challenges. Innovation, the child of necessity and imagination, has the transformative potential to not only reshape our daily lives but also to propel us towards a more sustainable and equitable future.\\n\\nFrom the desalination plants that convert seawater into life-sustaining fresh water to the drones that seed billions of trees in deforested regions, every revolutionary idea began as a mere spark in the human mind. It is this spark that has the power to cross borders, connect cultures, and confront crises—from climate change to global health concerns.\\n\\nTake the example of renewable energy; once a niche and expensive proposition, it now stands at the forefront of our fight against climate change. Innovations in solar and wind power technology have led to significant cost reductions, making them more accessible and feasible as a primary energy source. This shift not only promises cleaner air and a healthier planet but also jobs, economic growth, and a step towards energy independence for nations.\\n\\nAmidst a global health pandemic, we\\'ve witnessed an unprecedented acceleration in vaccine development. This leap forward was rooted in innovative technologies such as mRNA, which have changed the landscape of medicine and offer promising platforms for tackling infectious diseases that have plagued humanity for centuries.\\n\\nHowever, innovation doesn\\'t solely belong to the grandiose scientific breakthroughs that make headlines. It thrives in the quiet corners of daily life, in the processes improved by a factory worker\\'s suggestion, or in the homegrown solutions to local problems that spread globally—like microcredit systems that began in South Asia and are now employed worldwide to combat poverty.\\n\\nWhat all these examples highlight is that innovation serves as the link between recognizing a problem and forging a solution. It is not purely about technological advancements, but rather a mindset of approaching challenges with a can-do spirit, willingness to take risks, and a commitment to continuous improvement.\\n\\nIt is incumbent upon us as global citizens, policymakers, entrepreneurs, and educators to foster an environment that encourages creativity and rewards experimentation. By investing in education, promoting cross-disciplinary collaboration, and ensuring equitable access to resources, we can ensure that the fires of innovation remain ablaze.\\n\\nIt is through this collective commitment to cultivating and harnessing the power of innovation that we stand our best chance of solving the most pressing issues of our time. The road ahead is paved with uncertainty, but with the torch of innovation in hand, we can light the way to a brighter, more resilient tomorrow.\\n\\n---\\n\\nThis post touches on a ubiquitous theme—innovation—and its essential role in addressing the world’s most pressing challenges. I hope this sample aligns with your interests or inspires a specific direction for your blog post. If you provide me with more information on the desired context, I can further tailor the content to meet your needs.\\n\\n', cost=({'total_cost': 0.06972, 'gpt-4': {'cost': 0.06972, 'prompt_tokens': 542, 'completion_tokens': 891, 'total_tokens': 1433}}, {'total_cost': 0}), human_input=[])}" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "financial_assistant_2 = autogen.AssistantAgent(\n", + " name=\"Financial_assistant_2\",\n", + " llm_config={\"config_list\": config_list},\n", + ")\n", + "writer = autogen.AssistantAgent(\n", + " name=\"Writer\",\n", + " llm_config={\"config_list\": config_list},\n", + " is_termination_msg=lambda x: x.get(\"content\", \"\").find(\"TERMINATE\") >= 0,\n", + " system_message=\"\"\"\n", + " You are a professional writer, known for\n", + " your insightful and engaging articles.\n", + " You transform complex concepts into compelling narratives.\n", + " Reply \"TERMINATE\" in the end when everything is done.\n", + " \"\"\",\n", + ")\n", + "\n", + "financial_assistant_2.register_nested_chats(\n", + " [autogen.Agent, None], [{\"recipient\": manager, \"summary_method\": \"reflection_with_llm\"}]\n", + ")\n", + "user.initiate_chats(\n", + " [\n", + " {\n", + " \"recipient\": financial_assistant_1,\n", + " \"message\": financial_tasks[0],\n", + " \"clear_history\": True,\n", + " \"silent\": False,\n", + " \"takeaway_method\": \"reflection_with_llm\",\n", + " },\n", + " {\n", + " \"recipient\": financial_assistant_2,\n", + " \"message\": financial_tasks[1],\n", + " \"takeaway_method\": \"reflection_with_llm\",\n", + " },\n", + " {\n", + " \"recipient\": writer,\n", + " \"message\": writing_tasks[0],\n", + " },\n", + " ]\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.10.13" + }, + "vscode": { + "interpreter": { + "hash": "949777d72b0d2535278d3dc13498b2535136f6dfe0678499012e853ee9abcab1" + } + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": { + "2d910cfd2d2a4fc49fc30fbbdc5576a7": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "454146d0f7224f038689031002906e6f": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_e4ae2b6f5a974fd4bafb6abb9d12ff26", + "IPY_MODEL_577e1e3cc4db4942b0883577b3b52755", + "IPY_MODEL_b40bdfb1ac1d4cffb7cefcb870c64d45" + ], + "layout": "IPY_MODEL_dc83c7bff2f241309537a8119dfc7555", + "tabbable": null, + "tooltip": null + } + }, + "577e1e3cc4db4942b0883577b3b52755": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_2d910cfd2d2a4fc49fc30fbbdc5576a7", + "max": 1, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_74a6ba0c3cbc4051be0a83e152fe1e62", + "tabbable": null, + "tooltip": null, + "value": 1 + } + }, + "6086462a12d54bafa59d3c4566f06cb2": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "74a6ba0c3cbc4051be0a83e152fe1e62": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "7d3f3d9e15894d05a4d188ff4f466554": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "background": null, + "description_width": "", + "font_size": null, + "text_color": null + } + }, + "b40bdfb1ac1d4cffb7cefcb870c64d45": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HTMLView", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_f1355871cc6f4dd4b50d9df5af20e5c8", + "placeholder": "​", + "style": "IPY_MODEL_ca245376fd9f4354af6b2befe4af4466", + "tabbable": null, + "tooltip": null, + "value": " 1/1 [00:00<00:00, 44.69it/s]" + } + }, + "ca245376fd9f4354af6b2befe4af4466": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "background": null, + "description_width": "", + "font_size": null, + "text_color": null + } + }, + "dc83c7bff2f241309537a8119dfc7555": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "e4ae2b6f5a974fd4bafb6abb9d12ff26": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HTMLView", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_6086462a12d54bafa59d3c4566f06cb2", + "placeholder": "​", + "style": "IPY_MODEL_7d3f3d9e15894d05a4d188ff4f466554", + "tabbable": null, + "tooltip": null, + "value": "100%" + } + }, + "f1355871cc6f4dd4b50d9df5af20e5c8": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + } + }, + "version_major": 2, + "version_minor": 0 + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebook/agentchat_nestedchat_chess.ipynb b/notebook/agentchat_nestedchat_chess.ipynb new file mode 100644 index 00000000000..9910ae26005 --- /dev/null +++ b/notebook/agentchat_nestedchat_chess.ipynb @@ -0,0 +1,944 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Auto Generated Agent Chat: Society of Minds\n", + "\n", + "This notebook showcases how to use conversable agents in AutoGen to realize a Society of Mind (SoM) scenario, in which an agent runs a chat session with another agent as an inner-monologue. The idea of SoM and part of the example demonstrated in this notebook come from this PR: https://github.com/microsoft/autogen/pull/890\n", + " \n", + "\n", + "## Requirements\n", + "\n", + "AutoGen requires `Python>=3.8`. To run this notebook example, please install:\n", + "```bash\n", + "pip install pyautogen\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%%capture --no-stderr\n", + "# %pip install \"pyautogen>=0.2.3\"\n", + "from collections import defaultdict\n", + "from typing import Any, Dict, List, Optional, Union\n", + "\n", + "import chess\n", + "import chess.svg\n", + "\n", + "import autogen\n", + "\n", + "%pip install chess -U" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Conversational Chess with Nested Chat" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "config_list_gpt4 = autogen.config_list_from_json(\n", + " \"OAI_CONFIG_LIST\",\n", + " # filter_dict={\n", + " # \"model\": [\"gpt-4\", \"gpt4\", \"gpt-4-32k\", \"gpt-4-32k-0314\", \"gpt-4-32k-v0314\"],\n", + " # },\n", + ")\n", + "\n", + "max_turn = 5\n", + "\n", + "sys_msg_tmpl = \"\"\"Your name is {name} and you are a chess player.\n", + "You are playing against {opponent_name}.\n", + "You are playing as {color}.\n", + "You communicate your move using universal chess interface language.\n", + "You also chit-chat with your opponent when you communicate a move to light up the mood.\n", + "You should ensure both you and the opponent are making legal moves.\n", + "Do not apologize for making illegal moves.\"\"\"\n", + "\n", + "board_sysm_msg = \"\"\"You are an AI-powered chess board agent.\n", + "You translate the user's natural language input into legal UCI moves.\n", + "You should only reply with a UCI move string extracted from the user's input.\"\"\"\n", + "\n", + "color_white = \"white\"\n", + "color_black = \"black\"\n", + "white_player_name = \"PlayerWhite\"\n", + "black_player_name = \"PlayerBlack\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Define Agents" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "class BoardAgent(autogen.AssistantAgent):\n", + " board: chess.Board = chess.Board()\n", + " correct_move_messages: Dict[autogen.Agent, List[Dict]] = defaultdict(list)\n", + " _reply_num: int = 0\n", + "\n", + " def set_correct_move_messages(self, sender, message, uci_move):\n", + " self.correct_move_messages[sender].extend([message, self._message_to_dict(uci_move)])\n", + "\n", + " def update_reply_num(self):\n", + " self._reply_num += 1\n", + "\n", + " @property\n", + " def reply_num(self):\n", + " return self._reply_num\n", + "\n", + "\n", + "board_agent = BoardAgent(\n", + " name=\"BoardAgent\",\n", + " system_message=board_sysm_msg,\n", + " llm_config={\"temperature\": 0.0, \"config_list\": config_list_gpt4},\n", + " max_consecutive_auto_reply=max_turn,\n", + ")\n", + "\n", + "player_white = autogen.AssistantAgent(\n", + " white_player_name,\n", + " system_message=sys_msg_tmpl.format(\n", + " name=white_player_name,\n", + " opponent_name=black_player_name,\n", + " color=color_white,\n", + " ),\n", + " llm_config={\"temperature\": 0.5, \"cache_seed\": 1, \"config_list\": config_list_gpt4},\n", + " max_consecutive_auto_reply=max_turn,\n", + ")\n", + "\n", + "player_black = autogen.AssistantAgent(\n", + " black_player_name,\n", + " system_message=sys_msg_tmpl.format(\n", + " name=black_player_name,\n", + " opponent_name=white_player_name,\n", + " color=color_black,\n", + " ),\n", + " llm_config={\"temperature\": 0.5, \"cache_seed\": 1, \"config_list\": config_list_gpt4},\n", + " max_consecutive_auto_reply=max_turn,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Register reply function and nested chats" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", + "\n", + "Your turn.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "Your turn.\n", + "\n", + "With the following carryover: \n", + "Current board:\n", + "r n b q k b n r\n", + "p p p p p p p p\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + "P P P P P P P P\n", + "R N B Q K B N R\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "Your turn.\n", + "Context: \n", + "Current board:\n", + "r n b q k b n r\n", + "p p p p p p p p\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + "P P P P P P P P\n", + "R N B Q K B N R\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "data": { + "image/svg+xml": [ + "
r n b q k b n r\n",
+       "p p p p p p p p\n",
+       ". . . . . . . .\n",
+       ". . . . . . . .\n",
+       ". . . . P . . .\n",
+       ". . . . . . . .\n",
+       "P P P P . P P P\n",
+       "R N B Q K B N R
" + ], + "text/plain": [ + "'
r n b q k b n r\\np p p p p p p p\\n. . . . . . . .\\n. . . . . . . .\\n. . . . P . . .\\n. . . . . . . .\\nP P P P . P P P\\nR N B Q K B N R
'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "e2e4\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", + "\n", + "e2e4\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "e2e4\n", + "\n", + "With the following carryover: \n", + "Current board:\n", + "r n b q k b n r\n", + "p p p p p p p p\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + ". . . . P . . .\n", + ". . . . . . . .\n", + "P P P P . P P P\n", + "R N B Q K B N R\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\n", + "e2e4\n", + "Context: \n", + "Current board:\n", + "r n b q k b n r\n", + "p p p p p p p p\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + ". . . . P . . .\n", + ". . . . . . . .\n", + "P P P P . P P P\n", + "R N B Q K B N R\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "\n", + "Error: illegal uci: 'e2e4' in rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\n", + "My apologies, I got ahead of myself! It's your move, PlayerBlack. The board is set for your first move. Let's have a great game!\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "data": { + "image/svg+xml": [ + "
r n b q k b n r\n",
+       "p p p p . p p p\n",
+       ". . . . . . . .\n",
+       ". . . . p . . .\n",
+       ". . . . P . . .\n",
+       ". . . . . . . .\n",
+       "P P P P . P P P\n",
+       "R N B Q K B N R
" + ], + "text/plain": [ + "'
r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . . . .\\nP P P P . P P P\\nR N B Q K B N R
'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "\n", + "e7e5\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", + "\n", + "e7e5\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "e7e5\n", + "\n", + "With the following carryover: \n", + "Current board:\n", + "r n b q k b n r\n", + "p p p p . p p p\n", + ". . . . . . . .\n", + ". . . . p . . .\n", + ". . . . P . . .\n", + ". . . . . . . .\n", + "P P P P . P P P\n", + "R N B Q K B N R\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "e7e5\n", + "Context: \n", + "Current board:\n", + "r n b q k b n r\n", + "p p p p . p p p\n", + ". . . . . . . .\n", + ". . . . p . . .\n", + ". . . . P . . .\n", + ". . . . . . . .\n", + "P P P P . P P P\n", + "R N B Q K B N R\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "data": { + "image/svg+xml": [ + "
r n b q k b n r\n",
+       "p p p p . p p p\n",
+       ". . . . . . . .\n",
+       ". . . . p . . .\n",
+       ". . . . P . . .\n",
+       ". . . . . N . .\n",
+       "P P P P . P P P\n",
+       "R N B Q K B . R
" + ], + "text/plain": [ + "'
r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . N . .\\nP P P P . P P P\\nR N B Q K B . R
'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "g1f3\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", + "\n", + "g1f3\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "g1f3\n", + "\n", + "With the following carryover: \n", + "Current board:\n", + "r n b q k b n r\n", + "p p p p . p p p\n", + ". . . . . . . .\n", + ". . . . p . . .\n", + ". . . . P . . .\n", + ". . . . . N . .\n", + "P P P P . P P P\n", + "R N B Q K B . R\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\n", + "g1f3\n", + "Context: \n", + "Current board:\n", + "r n b q k b n r\n", + "p p p p . p p p\n", + ". . . . . . . .\n", + ". . . . p . . .\n", + ". . . . P . . .\n", + ". . . . . N . .\n", + "P P P P . P P P\n", + "R N B Q K B . R\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "data": { + "image/svg+xml": [ + "
r n b q k b . r\n",
+       "p p p p . p p p\n",
+       ". . . . . n . .\n",
+       ". . . . p . . .\n",
+       ". . . . P . . .\n",
+       ". . . . . N . .\n",
+       "P P P P . P P P\n",
+       "R N B Q K B . R
" + ], + "text/plain": [ + "'
r n b q k b . r\\np p p p . p p p\\n. . . . . n . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . N . .\\nP P P P . P P P\\nR N B Q K B . R
'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "\n", + "g8f6\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", + "\n", + "g8f6\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "data": { + "text/plain": [ + "ChatResult(chat_history=[{'content': 'Your turn.', 'role': 'assistant'}, {'content': 'e2e4', 'role': 'user'}, {'content': 'e7e5', 'role': 'assistant'}, {'content': 'g1f3', 'role': 'user'}, {'content': 'g8f6', 'role': 'assistant'}], summary='g8f6', cost=({'total_cost': 0.00885, 'gpt-4': {'cost': 0.00885, 'prompt_tokens': 229, 'completion_tokens': 33, 'total_tokens': 262}}, {'total_cost': 0}), human_input=[])" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def player2board_reply(recipient, messages, sender, config):\n", + " board = config if config else \"\"\n", + " # add a system message about the current state of the board.\n", + " board_state_msg = [{\"role\": \"system\", \"content\": f\"Current board:\\n{board}\"}]\n", + " last_message = messages[-1]\n", + " if last_message[\"content\"].startswith(\"Error\"):\n", + " # try again\n", + " # last_message[\"role\"] = \"system\"\n", + " # return True, messages + board_state_msg\n", + " # TODO: better way to handle this\n", + " _, rep = recipient.generate_oai_reply(messages + board_state_msg, sender)\n", + " return True, rep\n", + " else:\n", + " return True, None\n", + "\n", + "\n", + "def board_reply(recipient, messages, sender, config):\n", + " # print(\"resplyssss\")\n", + " if recipient.reply_num >= max_turn:\n", + " return True, None\n", + " message = messages[-1]\n", + " # extract a UCI move from player's message\n", + " # TODO: better way to handle this\n", + " _, reply = recipient.generate_oai_reply(recipient.correct_move_messages[sender] + [message], sender)\n", + " # reply = messages[-1][\"content\"]\n", + " uci_move = reply if isinstance(reply, str) else str(reply[\"content\"])\n", + " recipient.update_reply_num()\n", + " try:\n", + " recipient.board.push_uci(uci_move)\n", + " except ValueError as e:\n", + " # invalid move\n", + " return True, f\"Error: {e}\"\n", + " else:\n", + " # valid move\n", + " m = chess.Move.from_uci(uci_move)\n", + " try:\n", + " display( # noqa: F821\n", + " chess.svg.board(\n", + " recipient.board, arrows=[(m.from_square, m.to_square)], fill={m.from_square: \"gray\"}, size=200\n", + " )\n", + " )\n", + " except NameError as e:\n", + " print(f\"Error displaying board: {e}\")\n", + " # better way to handle this\n", + " recipient.set_correct_move_messages(sender, message, uci_move)\n", + " recipient.correct_move_messages[sender][-1][\"role\"] = \"assistant\"\n", + " return True, uci_move\n", + "\n", + "\n", + "def board_chat_func(chat_queue, recipient, messages, sender, config):\n", + " # TODO: better way to handle this\n", + " if chat_queue[0][\"recipient\"].reply_num >= max_turn:\n", + " return True, None\n", + " msg_content = messages[-1].get(\"content\", \"\")\n", + " # board = config\n", + " board = chat_queue[0][\"recipient\"].board\n", + " # if msg_content.startswith(\"Error\"):\n", + " if msg_content:\n", + " # TODO: how to handle message if there are multiple chats (doing this for the first chat for now)\n", + " c = chat_queue[0]\n", + " c[\"message\"] = msg_content\n", + " c[\"carryover\"] = f\"Current board:\\n{board}\" # NOTE: in the old code, this is added as system message\n", + "\n", + " chat_queue[0] = c\n", + " res = recipient.initiate_chats(chat_queue)\n", + " last_res = list(res.values())[-1]\n", + " return True, last_res.summary\n", + " else:\n", + " return True, None\n", + "\n", + "\n", + "board_agent.register_reply(autogen.Agent, board_reply, 0, ignore_async_in_sync_chat=True)\n", + "player_white.register_nested_chats(\n", + " [autogen.Agent, None],\n", + " [{\"recipient\": board_agent, \"summary_method\": \"last_msg\"}],\n", + " board_chat_func,\n", + " 0,\n", + " ignore_async_in_sync_chat=True,\n", + ")\n", + "player_black.register_nested_chats(\n", + " [autogen.Agent, None],\n", + " [{\"recipient\": board_agent, \"summary_method\": \"last_msg\"}],\n", + " board_chat_func,\n", + " 0,\n", + " ignore_async_in_sync_chat=True,\n", + ")\n", + "player_white.register_reply(BoardAgent, player2board_reply, ignore_async_in_sync_chat=True)\n", + "player_black.register_reply(BoardAgent, player2board_reply, ignore_async_in_sync_chat=True)\n", + "\n", + "player_white.initiate_chat(player_black, message=\"Your turn.\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.10.13" + }, + "vscode": { + "interpreter": { + "hash": "949777d72b0d2535278d3dc13498b2535136f6dfe0678499012e853ee9abcab1" + } + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": { + "2d910cfd2d2a4fc49fc30fbbdc5576a7": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "454146d0f7224f038689031002906e6f": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_e4ae2b6f5a974fd4bafb6abb9d12ff26", + "IPY_MODEL_577e1e3cc4db4942b0883577b3b52755", + "IPY_MODEL_b40bdfb1ac1d4cffb7cefcb870c64d45" + ], + "layout": "IPY_MODEL_dc83c7bff2f241309537a8119dfc7555", + "tabbable": null, + "tooltip": null + } + }, + "577e1e3cc4db4942b0883577b3b52755": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_2d910cfd2d2a4fc49fc30fbbdc5576a7", + "max": 1, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_74a6ba0c3cbc4051be0a83e152fe1e62", + "tabbable": null, + "tooltip": null, + "value": 1 + } + }, + "6086462a12d54bafa59d3c4566f06cb2": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "74a6ba0c3cbc4051be0a83e152fe1e62": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "7d3f3d9e15894d05a4d188ff4f466554": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "background": null, + "description_width": "", + "font_size": null, + "text_color": null + } + }, + "b40bdfb1ac1d4cffb7cefcb870c64d45": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HTMLView", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_f1355871cc6f4dd4b50d9df5af20e5c8", + "placeholder": "​", + "style": "IPY_MODEL_ca245376fd9f4354af6b2befe4af4466", + "tabbable": null, + "tooltip": null, + "value": " 1/1 [00:00<00:00, 44.69it/s]" + } + }, + "ca245376fd9f4354af6b2befe4af4466": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "background": null, + "description_width": "", + "font_size": null, + "text_color": null + } + }, + "dc83c7bff2f241309537a8119dfc7555": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "e4ae2b6f5a974fd4bafb6abb9d12ff26": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HTMLView", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_6086462a12d54bafa59d3c4566f06cb2", + "placeholder": "​", + "style": "IPY_MODEL_7d3f3d9e15894d05a4d188ff4f466554", + "tabbable": null, + "tooltip": null, + "value": "100%" + } + }, + "f1355871cc6f4dd4b50d9df5af20e5c8": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + } + }, + "version_major": 2, + "version_minor": 0 + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebook/agentchat_nestedchat_optiguide.ipynb b/notebook/agentchat_nestedchat_optiguide.ipynb new file mode 100644 index 00000000000..8d857f2d570 --- /dev/null +++ b/notebook/agentchat_nestedchat_optiguide.ipynb @@ -0,0 +1,1307 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Auto Generated Agent Chat: Society of Minds\n", + "\n", + "This notebook showcases how to use conversable agents in AutoGen to realize a Society of Mind (SoM) scenario, in which an agent runs a chat session with another agent as an inner-monologue. The idea of SoM and part of the example demonstrated in this notebook come from this PR: https://github.com/microsoft/autogen/pull/890\n", + " \n", + "\n", + "## Requirements\n", + "\n", + "AutoGen requires `Python>=3.8`. To run this notebook example, please install:\n", + "```bash\n", + "pip install pyautogen\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 65, + "metadata": {}, + "outputs": [], + "source": [ + "import autogen\n", + "from typing import Union\n", + "\n", + "# test Gurobi installation\n", + "from gurobipy import GRB\n", + "from eventlet.timeout import Timeout\n", + "import re\n", + "from termcolor import colored\n", + "from autogen.code_utils import extract_code\n", + "\n", + "# import auxillary packages\n", + "import requests # for loading the example source code\n", + "\n", + "config_list_gpt4 = autogen.config_list_from_json(\n", + " \"OAI_CONFIG_LIST\",\n", + " # filter_dict={\n", + " # \"model\": [\"gpt-4\", \"gpt4\", \"gpt-4-32k\", \"gpt-4-32k-0314\", \"gpt-4-32k-v0314\"],\n", + " # },\n", + ")\n", + "llm_config = {\"config_list\": config_list_gpt4}" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## OptiGuide with Nested Chat" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### System Messages" + ] + }, + { + "cell_type": "code", + "execution_count": 66, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "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", + ".\n", + ".\n", + ".\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": [ + "# %% System Messages\n", + "WRITER_SYSTEM_MSG = \"\"\"You are a chatbot to:\n", + "(1) write Python code to answer users questions for supply chain-related coding\n", + "project;\n", + "(2) explain solutions from a Gurobi/Python solver.\n", + "\n", + "--- SOURCE CODE ---\n", + "{source_code}\n", + "\n", + "--- DOC STR ---\n", + "{doc_str}\n", + "---\n", + "\n", + "Here are some example questions and their answers and codes:\n", + "--- EXAMPLES ---\n", + "{example_qa}\n", + "---\n", + "\n", + "The execution result of the original source code is below.\n", + "--- Original Result ---\n", + "{execution_result}\n", + "\n", + "Note that your written code will be added to the lines with substring:\n", + "\"# OPTIGUIDE *** CODE GOES HERE\"\n", + "So, you don't need to write other code, such as m.optimize() or m.update().\n", + "You just need to write code snippet in ```python ...``` block.\n", + "\"\"\"\n", + "\n", + "SAFEGUARD_SYSTEM_MSG = \"\"\"\n", + "Given the original source code:\n", + "{source_code}\n", + "\n", + "Is the following code safe (not malicious code to break security\n", + "and privacy) to run?\n", + "Answer only one word.\n", + "If not safe, answer `DANGER`; else, answer `SAFE`.\n", + "\"\"\"\n", + "\n", + "# %% Constant strings to match code lines in the source code.\n", + "DATA_CODE_STR = \"# OPTIGUIDE DATA CODE GOES HERE\"\n", + "CONSTRAINT_CODE_STR = \"# OPTIGUIDE CONSTRAINT CODE GOES HERE\"\n", + "\n", + "# In-context learning examples.\n", + "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", + "\"\"\"\n", + "\n", + "CODE_PROMPT = \"\"\"\n", + "Answer Code:\n", + "\"\"\"\n", + "\n", + "DEBUG_PROMPT = \"\"\"\n", + "\n", + "While running the code you suggested, I encountered the {error_type}:\n", + "--- ERROR MESSAGE ---\n", + "{error_message}\n", + "\n", + "Please try to resolve this bug, and rewrite the code snippet.\n", + "--- NEW CODE ---\n", + "\"\"\"\n", + "\n", + "SAFEGUARD_PROMPT = \"\"\"\n", + "--- Code ---\n", + "{code}\n", + "\n", + "--- One-Word Answer: SAFE or DANGER ---\n", + "\"\"\"\n", + "\n", + "INTERPRETER_PROMPT = \"\"\"Here are the execution results: {execution_rst}\n", + "\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\n", + "beginning.\n", + "\n", + "--- HUMAN READABLE ANSWER ---\n", + "\"\"\"\n", + "\n", + "# Get the source code of the coffee example from OptiGuide's official repo\n", + "code_url = \"https://raw.githubusercontent.com/microsoft/OptiGuide/main/benchmark/application/coffee.py\"\n", + "response = requests.get(code_url)\n", + "# Check if the request was successful\n", + "if response.status_code == 200:\n", + " # Get the text content from the response\n", + " code = response.text\n", + "else:\n", + " raise RuntimeError(\"Failed to retrieve the file.\")\n", + "# code = open(code_url, \"r\").read() # for local files\n", + "\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:]))\n", + "\n", + "writer_system_msg = WRITER_SYSTEM_MSG.format(\n", + " source_code=code,\n", + " doc_str=\"\",\n", + " example_qa=example_qa,\n", + " execution_result=\"\",\n", + ")\n", + "safeguard_system_msg = SAFEGUARD_SYSTEM_MSG.format(source_code=code)\n", + "# TODO: system message needs to be changed. Needs to add execution result of the original source code and user_chat_history\n", + "\n", + "\n", + "def replace(src_code: str, old_code: str, new_code: str) -> str:\n", + " \"\"\"\n", + " Inserts new code into the source code by replacing a specified old\n", + " code block.\n", + "\n", + " Args:\n", + " src_code (str): The source code to modify.\n", + " old_code (str): The code block to be replaced.\n", + " new_code (str): The new code block to insert.\n", + "\n", + " Returns:\n", + " str: The modified source code with the new code inserted.\n", + "\n", + " Raises:\n", + " None\n", + "\n", + " Example:\n", + " src_code = 'def hello_world():\\n print(\"Hello, world!\")\\n\\n# Some\n", + " other code here'\n", + " old_code = 'print(\"Hello, world!\")'\n", + " new_code = 'print(\"Bonjour, monde!\")\\nprint(\"Hola, mundo!\")'\n", + " modified_code = _replace(src_code, old_code, new_code)\n", + " print(modified_code)\n", + " # Output:\n", + " # def hello_world():\n", + " # print(\"Bonjour, monde!\")\n", + " # print(\"Hola, mundo!\")\n", + " # Some other code here\n", + " \"\"\"\n", + " pattern = r\"( *){old_code}\".format(old_code=old_code)\n", + " head_spaces = re.search(pattern, src_code, flags=re.DOTALL).group(1)\n", + " new_code = \"\\n\".join([head_spaces + line for line in new_code.split(\"\\n\")])\n", + " rst = re.sub(pattern, new_code, src_code)\n", + " return rst\n", + "\n", + "\n", + "def insert_code(src_code: str, new_lines: str) -> str:\n", + " \"\"\"insert a code patch into the source code.\n", + "\n", + "\n", + " Args:\n", + " src_code (str): the full source code\n", + " new_lines (str): The new code.\n", + "\n", + " Returns:\n", + " str: the full source code after insertion (replacement).\n", + " \"\"\"\n", + " if new_lines.find(\"addConstr\") >= 0:\n", + " return replace(src_code, CONSTRAINT_CODE_STR, new_lines)\n", + " else:\n", + " return replace(src_code, DATA_CODE_STR, new_lines)\n", + "\n", + "\n", + "def run_with_exec(src_code: str) -> Union[str, Exception]:\n", + " \"\"\"Run the code snippet with exec.\n", + "\n", + " Args:\n", + " src_code (str): The source code to run.\n", + "\n", + " Returns:\n", + " object: The result of the code snippet.\n", + " If the code succeed, returns the objective value (float or string).\n", + " else, return the error (exception)\n", + " \"\"\"\n", + " locals_dict = {}\n", + " locals_dict.update(globals())\n", + " locals_dict.update(locals())\n", + "\n", + " timeout = Timeout(\n", + " 60,\n", + " TimeoutError(\"This is a timeout exception, in case \" \"GPT's code falls into infinite loop.\"),\n", + " )\n", + " try:\n", + " exec(src_code, locals_dict, locals_dict)\n", + " except Exception as e:\n", + " return e\n", + " finally:\n", + " timeout.cancel()\n", + "\n", + " try:\n", + " status = locals_dict[\"m\"].Status\n", + " if status != GRB.OPTIMAL:\n", + " if status == GRB.UNBOUNDED:\n", + " ans = \"unbounded\"\n", + " elif status == GRB.INF_OR_UNBD:\n", + " ans = \"inf_or_unbound\"\n", + " elif status == GRB.INFEASIBLE:\n", + " ans = \"infeasible\"\n", + " m = locals_dict[\"m\"]\n", + " m.computeIIS()\n", + " constrs = [c.ConstrName for c in m.getConstrs() if c.IISConstr]\n", + " ans += \"\\nConflicting Constraints:\\n\" + str(constrs)\n", + " else:\n", + " ans = \"Model Status:\" + str(status)\n", + " else:\n", + " ans = \"Optimization problem solved. The objective value is: \" + str(locals_dict[\"m\"].objVal)\n", + " except Exception as e:\n", + " return e\n", + "\n", + " return ans" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Define Agents" + ] + }, + { + "cell_type": "code", + "execution_count": 67, + "metadata": {}, + "outputs": [], + "source": [ + "class OptiGuide(autogen.AssistantAgent):\n", + " source_code: str = code\n", + " debug_times: int = 3\n", + " debug_times_left: int = 3\n", + " example_qa: str = \"\"\n", + " success: bool = False\n", + " user_chat_history: str = \"\"\n", + "\n", + " def set_success(self, success: bool):\n", + " self.success = success\n", + "\n", + " @property\n", + " def get_success(self):\n", + " return self.success\n", + "\n", + " def update_debug_times(self):\n", + " self.debug_times_left -= 1\n", + "\n", + " def set_user_chat_history(self, user_chat_history: str):\n", + " self.user_chat_history = user_chat_history\n", + "\n", + "\n", + "class Writer(autogen.AssistantAgent):\n", + " source_code: str = code\n", + " debug_times: int = 3\n", + " debug_times_left: int = 3\n", + " example_qa: str = \"\"\n", + " user_chat_history: str = \"\"\n", + "\n", + " def set_user_chat_history(self, user_chat_history: str):\n", + " self.user_chat_history = user_chat_history\n", + "\n", + "\n", + "writer = Writer(\"writer\", llm_config=llm_config)\n", + "safeguard = autogen.AssistantAgent(\"safeguard\", llm_config=llm_config)\n", + "optiguide_commander = OptiGuide(\"commander\", llm_config=llm_config)\n", + "\n", + "user = autogen.UserProxyAgent(\n", + " \"user\", max_consecutive_auto_reply=0, human_input_mode=\"NEVER\", code_execution_config=False\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 68, + "metadata": {}, + "outputs": [], + "source": [ + "def nested_writer_reply(chat_queue, recipient, messages, sender, config):\n", + " # max_turn = 10\n", + " # if chat_queue[0][\"recipient\"].reply_num >= max_turn:\n", + " # return True, None\n", + " msg_content = messages[-1].get(\"content\", \"\")\n", + " # board = config\n", + " # get execution result of the original source code\n", + " sender_history = recipient.chat_messages[sender]\n", + " user_chat_history = \"\\nHere are the history of discussions:\\n\" f\"{sender_history}\"\n", + "\n", + " if sender.name == \"user\":\n", + " execution_result = msg_content # TODO: get the execution result of the original source code\n", + " else:\n", + " execution_result = \"\"\n", + " writer_sys_msg = (\n", + " WRITER_SYSTEM_MSG.format(\n", + " source_code=recipient.source_code,\n", + " doc_str=\"\",\n", + " example_qa=example_qa,\n", + " execution_result=execution_result,\n", + " )\n", + " + user_chat_history\n", + " )\n", + " # safeguard_sys_msg = (\n", + " # SAFEGUARD_SYSTEM_MSG.format(source_code=recipient.source_code)\n", + " # + user_chat_history\n", + " # )\n", + " # update system message\n", + " nested_agent = chat_queue[0][\"recipient\"]\n", + " nested_agent.update_system_message(writer_sys_msg)\n", + " print(\"nestss\", nested_agent.name)\n", + " nested_agent.set_user_chat_history(user_chat_history)\n", + " # safeguard.update_system_message(safeguard_sys_msg) #TODO: update system message for safeguard\n", + " nested_agent.reset()\n", + " # safeguard.reset() #TODO: reset safeguard\n", + " recipient.debug_times_left = recipient.debug_times\n", + " recipient.set_success(False)\n", + "\n", + " chat_queue[0][\"recipient\"] = nested_agent\n", + " chat_queue[0][\"message\"] = CODE_PROMPT\n", + "\n", + " chat_res = recipient.initiate_chats(chat_queue)\n", + "\n", + " if recipient.success:\n", + " reply = list(chat_res.values())[-1].summary\n", + " else:\n", + " reply = \"Sorry. I cannot answer your question.\"\n", + " return True, reply\n", + "\n", + "\n", + "def nested_safeguard_reply(chat_queue, recipient, messages, sender, config):\n", + " if recipient.success:\n", + " # no reply to writer\n", + " return True, None\n", + " # Step 3: safeguard\n", + "\n", + " safeguard_sys_msg = SAFEGUARD_SYSTEM_MSG.format(source_code=recipient.source_code) + recipient.user_chat_history\n", + "\n", + " chat_agent = chat_queue[0][\"recipient\"]\n", + " chat_agent.update_system_message(safeguard_sys_msg)\n", + " chat_agent.reset()\n", + " chat_queue[0][\"recipient\"] = chat_agent\n", + " # chat_agent[0][\"recipient\"].set_success(False)\n", + " last_msg_content = messages[-1].get(\"content\", \"\")\n", + " _, code = extract_code(last_msg_content)[0]\n", + " chat_info = chat_queue[0]\n", + " chat_info[\"message\"] = SAFEGUARD_SYSTEM_MSG.format(source_code=recipient.source_code)\n", + " chat_queue[0] = chat_info\n", + " res = recipient.initiate_chats(chat_queue)\n", + "\n", + " safe_msg = list(res.values())[-1].summary\n", + " if safe_msg.find(\"DANGER\") < 0:\n", + " # Step 4 and 5: Run the code and obtain the results\n", + " src_code = insert_code(recipient.source_code, code)\n", + " execution_rst = run_with_exec(src_code)\n", + " print(colored(str(execution_rst), \"yellow\"))\n", + " if type(execution_rst) in [str, int, float]:\n", + " # we successfully run the code and get the result\n", + " recipient.set_success(True)\n", + " # Step 6: request to interpret results\n", + " return True, INTERPRETER_PROMPT.format(execution_rst=execution_rst)\n", + " else:\n", + " # DANGER: If not safe, try to debug. Redo coding\n", + " execution_rst = \"\"\"\n", + " Sorry, this new code is not safe to run. I would not allow you to execute it.\n", + " Please try to find a new way (coding) to answer the question.\"\"\"\n", + " if recipient.debug_times_left > 0:\n", + " # Try to debug and write code again (back to step 2)\n", + " recipient.update_debug_times()\n", + " return True, DEBUG_PROMPT.format(error_type=type(execution_rst), error_message=str(execution_rst))\n", + "\n", + "\n", + "writer_chat_queue = [{\"recipient\": writer, \"summary_method\": \"reflection_with_llm\"}]\n", + "safeguard_chat_queue = [{\"recipient\": safeguard, \"summary_method\": \"last_msg\"}]\n", + "# writer is triggered only when receiving a message from the user\n", + "optiguide_commander.register_nested_chats(\"user\", writer_chat_queue, nested_writer_reply)\n", + "# safeguard is triggered only when receiving a message from the writer\n", + "optiguide_commander.register_nested_chats(\"writer\", safeguard_chat_queue, nested_safeguard_reply)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Let agents talk" + ] + }, + { + "cell_type": "code", + "execution_count": 69, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33muser\u001b[0m (to commander):\n", + "\n", + "What if we prohibit shipping from supplier 1 to roastery 2?\n", + "\n", + "--------------------------------------------------------------------------------\n", + "nestss writer\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "\n", + "Answer Code:\n", + "\n", + "\n", + "With the following carryover: \n", + "\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mcommander\u001b[0m (to writer):\n", + "\n", + "\n", + "Answer Code:\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mwriter\u001b[0m (to commander):\n", + "\n", + "```python\n", + "model.addConstr(x['supplier1', 'roastery2'] == 0, \"prohibit_s1_r2\")\n", + "```\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "\n", + "Given the original source code:\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", + "# Create a new model\n", + "model = Model(\"coffee_distribution\")\n", + "\n", + "# OPTIGUIDE DATA CODE GOES HERE\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] 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] 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] 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] for j in shipping_cost_from_roastery_to_cafe.keys()\n", + " if j[1] == c) >= dark_coffee_needed_for_cafe[c],\n", + " f\"dark_demand_{c}\")\n", + "\n", + "# Optimize model\n", + "model.optimize()\n", + "m = model\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", + "\n", + "Is the following code safe (not malicious code to break security\n", + "and privacy) to run?\n", + "Answer only one word.\n", + "If not safe, answer `DANGER`; else, answer `SAFE`.\n", + "\n", + "\n", + "With the following carryover: \n", + "\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mcommander\u001b[0m (to safeguard):\n", + "\n", + "\n", + "Given the original source code:\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", + "# Create a new model\n", + "model = Model(\"coffee_distribution\")\n", + "\n", + "# OPTIGUIDE DATA CODE GOES HERE\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] 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] 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] 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] for j in shipping_cost_from_roastery_to_cafe.keys()\n", + " if j[1] == c) >= dark_coffee_needed_for_cafe[c],\n", + " f\"dark_demand_{c}\")\n", + "\n", + "# Optimize model\n", + "model.optimize()\n", + "m = model\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", + "\n", + "Is the following code safe (not malicious code to break security\n", + "and privacy) to run?\n", + "Answer only one word.\n", + "If not safe, answer `DANGER`; else, answer `SAFE`.\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33msafeguard\u001b[0m (to commander):\n", + "\n", + "SAFE\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mcommander\u001b[0m (to safeguard):\n", + "\n", + "TERMINATE\n", + "\n", + "--------------------------------------------------------------------------------\n", + "Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n", + "\n", + "CPU model: Apple M3 Max\n", + "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", + "\n", + "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", + "Model fingerprint: 0xab8d681d\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.00 seconds (0.00 work units)\n", + "Thread count was 14 (of 14 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 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n", + "\n", + "CPU model: Apple M3 Max\n", + "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", + "\n", + "Optimize a model with 12 rows, 18 columns and 37 nonzeros\n", + "Model fingerprint: 0x5dab87a5\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 prohibit_s1_r2 by 80.000000000\n", + "\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", + "\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.00 seconds (0.00 work units)\n", + "Thread count was 14 (of 14 available processors)\n", + "\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", + "Thu Feb 8 22:01:13 2024\n", + "Optimal cost: 2760.0\n", + "\u001b[33mOptimization problem solved. The objective value is: 2760.0\u001b[0m\n", + "\u001b[33mcommander\u001b[0m (to writer):\n", + "\n", + "Here are the execution results: Optimization problem solved. The objective value is: 2760.0\n", + "\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\n", + "beginning.\n", + "\n", + "--- HUMAN READABLE ANSWER ---\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mwriter\u001b[0m (to commander):\n", + "\n", + "Certainly, the results from optimizing the supply chain model suggest the following:\n", + "\n", + "After adding the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was still successfully solved. The new objective value, which represents the total cost of the supply chain operation after implementing the constraint, is $2760.0.\n", + "\n", + "To provide a comparison with the original results, we should look at the objective value before the constraint was added. If the previous objective value is lower than $2760.0, this implies that the new constraint increased the total cost of operations, highlighting the economic impact of not using supplier 1 for roastery 2.\n", + "\n", + "On the other hand, if the previous objective value is higher than $2760.0, then despite the constraint, we have found a more cost-effective way to meet the supply and demand needs.\n", + "\n", + "Unfortunately, the original objective value prior to this constraint being added is not provided here. For a full comparison, you would need that original value to assess the financial impact of this constraint on the overall supply chain cost.\n", + "\n", + "--- HUMAN READABLE ANSWER ---\n", + "The new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mcommander\u001b[0m (to user):\n", + "\n", + "The optimization of the supply chain model with a constraint that prohibits shipping from supplier 1 to roastery 2 was successful, and the calculated total operation cost is $2760.0. To determine the impact of this constraint, a comparison with the original objective value is needed; however, it was not provided in the conversation. If the original value is lower, the constraint has increased costs; if it is higher, the supply chain might be more cost-effective despite the constraint.\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "data": { + "text/plain": [ + "ChatResult(chat_history=[{'content': 'What if we prohibit shipping from supplier 1 to roastery 2?', 'role': 'assistant'}, {'content': 'The optimization of the supply chain model with a constraint that prohibits shipping from supplier 1 to roastery 2 was successful, and the calculated total operation cost is $2760.0. To determine the impact of this constraint, a comparison with the original objective value is needed; however, it was not provided in the conversation. If the original value is lower, the constraint has increased costs; if it is higher, the supply chain might be more cost-effective despite the constraint.', 'role': 'user'}], summary='The optimization of the supply chain model with a constraint that prohibits shipping from supplier 1 to roastery 2 was successful, and the calculated total operation cost is $2760.0. To determine the impact of this constraint, a comparison with the original objective value is needed; however, it was not provided in the conversation. If the original value is lower, the constraint has increased costs; if it is higher, the supply chain might be more cost-effective despite the constraint.', cost=({'total_cost': 0.0465, 'gpt-4': {'cost': 0.0465, 'prompt_tokens': 1544, 'completion_tokens': 3, 'total_tokens': 1547}}, {'total_cost': 0}), human_input=[])" + ] + }, + "execution_count": 69, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "user.initiate_chat(optiguide_commander, message=\"What if we prohibit shipping from supplier 1 to roastery 2?\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.10.13" + }, + "vscode": { + "interpreter": { + "hash": "949777d72b0d2535278d3dc13498b2535136f6dfe0678499012e853ee9abcab1" + } + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": { + "2d910cfd2d2a4fc49fc30fbbdc5576a7": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "454146d0f7224f038689031002906e6f": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_e4ae2b6f5a974fd4bafb6abb9d12ff26", + "IPY_MODEL_577e1e3cc4db4942b0883577b3b52755", + "IPY_MODEL_b40bdfb1ac1d4cffb7cefcb870c64d45" + ], + "layout": "IPY_MODEL_dc83c7bff2f241309537a8119dfc7555", + "tabbable": null, + "tooltip": null + } + }, + "577e1e3cc4db4942b0883577b3b52755": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_2d910cfd2d2a4fc49fc30fbbdc5576a7", + "max": 1, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_74a6ba0c3cbc4051be0a83e152fe1e62", + "tabbable": null, + "tooltip": null, + "value": 1 + } + }, + "6086462a12d54bafa59d3c4566f06cb2": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "74a6ba0c3cbc4051be0a83e152fe1e62": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "7d3f3d9e15894d05a4d188ff4f466554": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "background": null, + "description_width": "", + "font_size": null, + "text_color": null + } + }, + "b40bdfb1ac1d4cffb7cefcb870c64d45": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HTMLView", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_f1355871cc6f4dd4b50d9df5af20e5c8", + "placeholder": "​", + "style": "IPY_MODEL_ca245376fd9f4354af6b2befe4af4466", + "tabbable": null, + "tooltip": null, + "value": " 1/1 [00:00<00:00, 44.69it/s]" + } + }, + "ca245376fd9f4354af6b2befe4af4466": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "background": null, + "description_width": "", + "font_size": null, + "text_color": null + } + }, + "dc83c7bff2f241309537a8119dfc7555": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "e4ae2b6f5a974fd4bafb6abb9d12ff26": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HTMLView", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_6086462a12d54bafa59d3c4566f06cb2", + "placeholder": "​", + "style": "IPY_MODEL_7d3f3d9e15894d05a4d188ff4f466554", + "tabbable": null, + "tooltip": null, + "value": "100%" + } + }, + "f1355871cc6f4dd4b50d9df5af20e5c8": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + } + }, + "version_major": 2, + "version_minor": 0 + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From cd7349feaa9af2cb291f1179cdcd52f8df73b0f8 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Thu, 8 Feb 2024 22:25:21 -0500 Subject: [PATCH 03/57] SoM notebook --- ...Mind copy.ipynb => agentchat_nestedchat_Society_of_Mind.ipynb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename notebook/{agentchat_nestedchat_Society_of_Mind copy.ipynb => agentchat_nestedchat_Society_of_Mind.ipynb} (100%) diff --git a/notebook/agentchat_nestedchat_Society_of_Mind copy.ipynb b/notebook/agentchat_nestedchat_Society_of_Mind.ipynb similarity index 100% rename from notebook/agentchat_nestedchat_Society_of_Mind copy.ipynb rename to notebook/agentchat_nestedchat_Society_of_Mind.ipynb From 3d63f9437847ab4ccf6c2e6d1087d9ee602868f4 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Thu, 8 Feb 2024 22:27:48 -0500 Subject: [PATCH 04/57] doc --- notebook/agentchat_nestedchat_chess.ipynb | 4 ++-- notebook/agentchat_nestedchat_optiguide.ipynb | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/notebook/agentchat_nestedchat_chess.ipynb b/notebook/agentchat_nestedchat_chess.ipynb index 9910ae26005..33b24a5e5bb 100644 --- a/notebook/agentchat_nestedchat_chess.ipynb +++ b/notebook/agentchat_nestedchat_chess.ipynb @@ -86,7 +86,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Define Agents" + "### Define Agents" ] }, { @@ -145,7 +145,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Register reply function and nested chats" + "### Register reply func and nested chats" ] }, { diff --git a/notebook/agentchat_nestedchat_optiguide.ipynb b/notebook/agentchat_nestedchat_optiguide.ipynb index 8d857f2d570..a3bd209e8d7 100644 --- a/notebook/agentchat_nestedchat_optiguide.ipynb +++ b/notebook/agentchat_nestedchat_optiguide.ipynb @@ -379,6 +379,13 @@ ")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Register reply func and nested chats" + ] + }, { "cell_type": "code", "execution_count": 68, From b8f00cc06d64488df7cce93e3607ce335f219c9d Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Thu, 8 Feb 2024 22:36:37 -0500 Subject: [PATCH 05/57] update notebook --- notebook/agentchat_nestedchat_Society_of_Mind.ipynb | 4 ++-- notebook/agentchat_nestedchat_chess.ipynb | 4 ++-- notebook/agentchat_nestedchat_optiguide.ipynb | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/notebook/agentchat_nestedchat_Society_of_Mind.ipynb b/notebook/agentchat_nestedchat_Society_of_Mind.ipynb index 9ae79d575de..1734004c4b6 100644 --- a/notebook/agentchat_nestedchat_Society_of_Mind.ipynb +++ b/notebook/agentchat_nestedchat_Society_of_Mind.ipynb @@ -9,7 +9,7 @@ } }, "source": [ - "# Auto Generated Agent Chat: Society of Minds\n", + "# Society of Minds with Nested Chats in AutoGen\n", "\n", "This notebook showcases how to use conversable agents in AutoGen to realize a Society of Mind (SoM) scenario, in which an agent runs a chat session with another agent as an inner-monologue. The idea of SoM and part of the example demonstrated in this notebook come from this PR: https://github.com/microsoft/autogen/pull/890\n", " \n", @@ -27,7 +27,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Society of Minds" + "## Society of Minds with Nested Chats" ] }, { diff --git a/notebook/agentchat_nestedchat_chess.ipynb b/notebook/agentchat_nestedchat_chess.ipynb index 33b24a5e5bb..e41ea988fd2 100644 --- a/notebook/agentchat_nestedchat_chess.ipynb +++ b/notebook/agentchat_nestedchat_chess.ipynb @@ -9,9 +9,9 @@ } }, "source": [ - "# Auto Generated Agent Chat: Society of Minds\n", + "# Conversational Chess with Nested Chats in AutoGen \n", "\n", - "This notebook showcases how to use conversable agents in AutoGen to realize a Society of Mind (SoM) scenario, in which an agent runs a chat session with another agent as an inner-monologue. The idea of SoM and part of the example demonstrated in this notebook come from this PR: https://github.com/microsoft/autogen/pull/890\n", + "This is a nested chat re-implementation of Conversational Chess referring to: https://github.com/microsoft/autogen/blob/nestchat/notebook/agentchat_nestedchat_chess.ipynb\n", " \n", "\n", "## Requirements\n", diff --git a/notebook/agentchat_nestedchat_optiguide.ipynb b/notebook/agentchat_nestedchat_optiguide.ipynb index a3bd209e8d7..e503a6b0a45 100644 --- a/notebook/agentchat_nestedchat_optiguide.ipynb +++ b/notebook/agentchat_nestedchat_optiguide.ipynb @@ -9,9 +9,9 @@ } }, "source": [ - "# Auto Generated Agent Chat: Society of Minds\n", + "# OptiGuide with Nested Chats in AutoGen \n", "\n", - "This notebook showcases how to use conversable agents in AutoGen to realize a Society of Mind (SoM) scenario, in which an agent runs a chat session with another agent as an inner-monologue. The idea of SoM and part of the example demonstrated in this notebook come from this PR: https://github.com/microsoft/autogen/pull/890\n", + "This is a nested chat re-implementation of OptiGuide referring to: https://github.com/qingyun-wu/autogen-eval/blob/main/application/A4-multi-agent-coding/optiguide_example.ipynb\n", " \n", "\n", "## Requirements\n", From cb2924eda4d0bfa185dd79b1e3de30b00acbde22 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Thu, 8 Feb 2024 22:38:04 -0500 Subject: [PATCH 06/57] SoM --- ...agentchat_nestedchat_Society_of_Mind.ipynb | 398 ------------------ 1 file changed, 398 deletions(-) diff --git a/notebook/agentchat_nestedchat_Society_of_Mind.ipynb b/notebook/agentchat_nestedchat_Society_of_Mind.ipynb index 1734004c4b6..2e2f84e616f 100644 --- a/notebook/agentchat_nestedchat_Society_of_Mind.ipynb +++ b/notebook/agentchat_nestedchat_Society_of_Mind.ipynb @@ -98,13 +98,6 @@ ")" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Example 1: Solve one task with SoM" - ] - }, { "cell_type": "code", "execution_count": 3, @@ -288,397 +281,6 @@ ")\n", "user.initiate_chat(financial_assistant_1, message=financial_tasks[0])" ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Example 2: Solve multiple tasks with SoM" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[34mStart a new chat with the following message: \n", - "On which days this year was Microsoft Stock higher than $400? Put results in a table.\n", - "\n", - "With the following carryover: \n", - "\u001b[0m\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[33mUser\u001b[0m (to Financial_assistant_1):\n", - "\n", - "On which days this year was Microsoft Stock higher than $400? Put results in a table.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[34mStart a new chat with the following message: \n", - "On which days this year was Microsoft Stock higher than $400? Put results in a table.\n", - "\n", - "With the following carryover: \n", - "\u001b[0m\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[33mFinancial_assistant_1\u001b[0m (to chat_manager):\n", - "\n", - "On which days this year was Microsoft Stock higher than $400? Put results in a table.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", - "\n", - "To determine on which days Microsoft (ticker symbol: MSFT) stock was trading higher than $400 this year, I will use Python with the `pandas` library and the `yfinance` package to fetch historical stock price data from Yahoo Finance. To get this data, you must first ensure that `pandas` and `yfinance` are installed in your Python environment. If they're not installed, you can install them using pip.\n", - "\n", - "Here's a step-by-step plan we're going to follow:\n", - "\n", - "1. Import the required libraries.\n", - "2. Set up the ticker symbol for Microsoft.\n", - "3. Define the date range for \"this year\" until today.\n", - "4. Use yfinance to download the stock data for Microsoft.\n", - "5. Filter the data to find the days when the stock was higher than $400.\n", - "6. Print out the result in a tabular format.\n", - "\n", - "Please run the following code:\n", - "\n", - "```python\n", - "# filename: msft_stock_days_above_400.py\n", - "import pandas as pd\n", - "import yfinance as yf\n", - "from datetime import datetime\n", - "\n", - "# Ensure that pandas and yfinance are installed\n", - "try:\n", - " import pandas\n", - " import yfinance\n", - "except ImportError as e:\n", - " print(\"Please install pandas and yfinance using 'pip install pandas yfinance'\")\n", - " raise e\n", - "\n", - "def find_msft_days_above_400():\n", - " # Ticker symbol for Microsoft\n", - " ticker_symbol = \"MSFT\"\n", - " \n", - " # get the current year\n", - " current_year = datetime.now().year\n", - "\n", - " # Define the date range for this year until today\n", - " start_date = f\"{current_year}-01-01\"\n", - " end_date = datetime.now().strftime('%Y-%m-%d')\n", - "\n", - " # Download the stock price data\n", - " msft_data = yf.download(ticker_symbol, start=start_date, end=end_date)\n", - "\n", - " # Filter the data for days when the closing price was higher than $400\n", - " days_above_400 = msft_data[msft_data['Close'] > 400]\n", - "\n", - " # Check if there are any days that the closing price was above $400\n", - " if not days_above_400.empty:\n", - " # Print the result in a tabular format\n", - " print(days_above_400[['Close']].to_markdown())\n", - " else:\n", - " print(f\"No days in {current_year} where the Microsoft stock closed above $400.\")\n", - "\n", - "if __name__ == \"__main__\":\n", - " find_msft_days_above_400()\n", - "```\n", - "\n", - "Please save the above code to a file and run it using Python. It will print the days when Microsoft stock was higher than $400 this year, if any, in a markdown table format. If the stock has not surpassed that value this year, the script will tell you so.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[31m\n", - ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n", - "\u001b[33mInner-code-interpreter\u001b[0m (to chat_manager):\n", - "\n", - "exitcode: 0 (execution succeeded)\n", - "Code output: \n", - "| Date | Close |\n", - "|:--------------------|--------:|\n", - "| 2024-01-24 00:00:00 | 402.56 |\n", - "| 2024-01-25 00:00:00 | 404.87 |\n", - "| 2024-01-26 00:00:00 | 403.93 |\n", - "| 2024-01-29 00:00:00 | 409.72 |\n", - "| 2024-01-30 00:00:00 | 408.59 |\n", - "| 2024-02-01 00:00:00 | 403.78 |\n", - "| 2024-02-02 00:00:00 | 411.22 |\n", - "| 2024-02-05 00:00:00 | 405.65 |\n", - "| 2024-02-06 00:00:00 | 405.49 |\n", - "| 2024-02-07 00:00:00 | 414.05 |\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", - "\n", - "Great, the Python script successfully retrieved the data! Here's the table with the results:\n", - "\n", - "| Date | Close |\n", - "|:--------------------|--------:|\n", - "| 2024-01-24 00:00:00 | 402.56 |\n", - "| 2024-01-25 00:00:00 | 404.87 |\n", - "| 2024-01-26 00:00:00 | 403.93 |\n", - "| 2024-01-29 00:00:00 | 409.72 |\n", - "| 2024-01-30 00:00:00 | 408.59 |\n", - "| 2024-02-01 00:00:00 | 403.78 |\n", - "| 2024-02-02 00:00:00 | 411.22 |\n", - "| 2024-02-05 00:00:00 | 405.65 |\n", - "| 2024-02-06 00:00:00 | 405.49 |\n", - "| 2024-02-07 00:00:00 | 414.05 |\n", - "\n", - "These are the dates in 2024 when Microsoft's stock closed above $400. If you need more detailed information or further assistance, please let me know!\n", - "\n", - "TERMINATE\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mFinancial_assistant_1\u001b[0m (to User):\n", - "\n", - "Microsoft's stock closed above $400 on the following dates in 2024: January 24th ($402.56), January 25th ($404.87), January 26th ($403.93), January 29th ($409.72), January 30th ($408.59), February 1st ($403.78), February 2nd ($411.22), February 5th ($405.65), February 6th ($405.49), and February 7th ($414.05).\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mUser\u001b[0m (to Financial_assistant_1):\n", - "\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[34mStart a new chat with the following message: \n", - "Investigate the possible reasons of the stock performance.\n", - "\n", - "With the following carryover: \n", - "\u001b[0m\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[33mUser\u001b[0m (to Financial_assistant_2):\n", - "\n", - "Investigate the possible reasons of the stock performance.\n", - "Context: \n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[34mStart a new chat with the following message: \n", - "Investigate the possible reasons of the stock performance.\n", - "Context: \n", - "\n", - "\n", - "With the following carryover: \n", - "\u001b[0m\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[33mFinancial_assistant_2\u001b[0m (to chat_manager):\n", - "\n", - "Investigate the possible reasons of the stock performance.\n", - "Context: \n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", - "\n", - "To investigate the possible reasons for a stock's performance, we need to collect and analyze a variety of data. This could include the company's financial statements, recent news, market trends, sector performance, and analyst ratings. Let's break this down into a step-by-step plan:\n", - "\n", - "1. Identify the specific stock that you want to investigate.\n", - "2. Collect the most recent financial statements of the company to analyze its financial health.\n", - "3. Search for recent news articles and press releases related to the company, which may include information on earnings reports, product launches, partnerships, or any corporate actions that could affect the stock performance.\n", - "4. Look at overall market trends to see how they may be impacting the stock. This includes broader economic indicators and market sentiment that can influence investor decisions.\n", - "5. Compare the stock's performance with its sector or industry to determine if the movement is company-specific or part of a larger trend.\n", - "6. Check analyst ratings and forecasts for the stock, as these can influence investor expectations and sentiment.\n", - "\n", - "Since you're asking for financial assistance related to stock performance, I assume you're looking for factual data and analysis. As my capabilities do not extend to accessing real-time financial databases or proprietary news feeds, let's start by identifying the stock symbol and looking up recent news and market data from freely available online sources.\n", - "\n", - "If you provide the stock symbol, we can proceed with generating Python code that will fetch recent news headlines and financial data using available APIs or web scraping, if the data source permits it. This would be the code step. After that, I could use my language skills to help you interpret the gathered data.\n", - "\n", - "Please provide the stock symbol or company name you would like to investigate, and confirm if you're comfortable with proceeding in this way.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mInner-code-interpreter\u001b[0m (to chat_manager):\n", - "\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", - "\n", - "It looks like \"Inner-code-interpreter\" does not refer to a valid stock symbol. A stock symbol is usually 1 to 5 letters and represents a public company's stock that is traded on an exchange. It seems there might be confusion about the term \"Inner-code-interpreter,\" and it's possible you meant something else.\n", - "\n", - "Please provide the correct stock symbol or clarify your request. Once I have the correct stock symbol, I can help you create a plan on how to proceed with investigating the stock's performance by fetching the relevant data for analysis.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mInner-code-interpreter\u001b[0m (to chat_manager):\n", - "\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", - "\n", - "I apologize for any confusion, but it appears that there might be a miscommunication. If \"Inner-code-interpreter\" is intended to be a directive or a stock symbol, it does not match any known stock ticker symbols or standard commands within the context of stock market research.\n", - "\n", - "To provide you with the assistance you are seeking, I would need the ticker symbol of the stock you want to investigate. The ticker symbol is an abbreviation used to uniquely identify publicly traded shares of a particular stock on a particular stock market.\n", - "\n", - "For example, to investigate the performance of Apple Inc., you would provide its ticker symbol AAPL.\n", - "\n", - "If by \"Inner-code-interpreter\" you are referring to the functionality of this text-based interface or to my role as an AI assistant designed to execute code internally, then please provide guidance on the actual task you would like me to perform, which in this case would be the stock symbol or company name you seek to investigate.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mInner-code-interpreter\u001b[0m (to chat_manager):\n", - "\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", - "\n", - "It seems you are referring to the functionality of this interface or to my capability as an AI to interpret and execute code. Since this is not directly related to investigating the performance of a stock, I need you to either provide a valid stock symbol for analysis or clarify the task at hand.\n", - "\n", - "If your intention is to analyze the performance of a specific stock, please provide the ticker symbol or company name, and I can assist with that process.\n", - "\n", - "If \"Inner-code-interpreter\" is meant as an instruction or a placeholder for another task, please provide the actual details of the task or instruction you need assistance with.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mFinancial_assistant_2\u001b[0m (to User):\n", - "\n", - "There has been a miscommunication regarding the term \"Inner-code-interpreter,\" which does not match any known stock ticker symbols or standard commands related to financial assistance or stock performance investigation. To proceed with the analysis of stock performance, the correct stock symbol or company name is required. The user has not provided this information, and thus the financial analysis of a specific stock has not been initiated. Instead, an explanation of how the user can proceed with the analysis upon providing the necessary information has been offered.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mUser\u001b[0m (to Financial_assistant_2):\n", - "\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[34mStart a new chat with the following message: \n", - "Develop an engaging blog post using any information provided.\n", - "\n", - "With the following carryover: \n", - "\n", - "\u001b[0m\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[33mUser\u001b[0m (to Writer):\n", - "\n", - "Develop an engaging blog post using any information provided.\n", - "Context: \n", - "\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mWriter\u001b[0m (to User):\n", - "\n", - "I'm happy to create an engaging blog post for you, however, it seems there's been an oversight, and no information or context was provided for the content you're looking for. Could you please provide some details or a topic you're interested in? This could relate to anything from technology, lifestyle, culture, science, personal development, travel, health, and more. Once I have that, I'll craft a compelling narrative for your intended audience.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mUser\u001b[0m (to Writer):\n", - "\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mWriter\u001b[0m (to User):\n", - "\n", - "It appears that your request may have been inadvertently left blank, thus not providing me with a specific theme or topic for developing a blog post. In order to assist you effectively, I need some sort of context or area of interest to focus on.\n", - "\n", - "You may want to consider topics such as emerging technology trends, the impact of social media on communication, the importance of sustainable living practices, or the benefits of mindfulness in today's fast-paced world, among others.\n", - "\n", - "Please provide me with the direction you'd like to take or any specific points you wish to address in your blog post. Once I have this information, I can proceed to craft an engaging and insightful narrative for your audience.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mUser\u001b[0m (to Writer):\n", - "\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mWriter\u001b[0m (to User):\n", - "\n", - "It seems like there might be a communication issue since I haven't received any additional details or context for the blog post. In the spirit of creativity and to provide you with a sample of my work, I will proceed to write an engaging piece on a topic that has universal relevance: \"The Power of Innovation in Solving Global Challenges.\"\n", - "\n", - "---\n", - "\n", - "**Igniting Change: The Power of Innovation in Solving Global Challenges**\n", - "\n", - "In a world that's smaller and more connected than ever, human ingenuity has emerged as the most potent tool in our arsenal as we confront a host of global challenges. Innovation, the child of necessity and imagination, has the transformative potential to not only reshape our daily lives but also to propel us towards a more sustainable and equitable future.\n", - "\n", - "From the desalination plants that convert seawater into life-sustaining fresh water to the drones that seed billions of trees in deforested regions, every revolutionary idea began as a mere spark in the human mind. It is this spark that has the power to cross borders, connect cultures, and confront crises—from climate change to global health concerns.\n", - "\n", - "Take the example of renewable energy; once a niche and expensive proposition, it now stands at the forefront of our fight against climate change. Innovations in solar and wind power technology have led to significant cost reductions, making them more accessible and feasible as a primary energy source. This shift not only promises cleaner air and a healthier planet but also jobs, economic growth, and a step towards energy independence for nations.\n", - "\n", - "Amidst a global health pandemic, we've witnessed an unprecedented acceleration in vaccine development. This leap forward was rooted in innovative technologies such as mRNA, which have changed the landscape of medicine and offer promising platforms for tackling infectious diseases that have plagued humanity for centuries.\n", - "\n", - "However, innovation doesn't solely belong to the grandiose scientific breakthroughs that make headlines. It thrives in the quiet corners of daily life, in the processes improved by a factory worker's suggestion, or in the homegrown solutions to local problems that spread globally—like microcredit systems that began in South Asia and are now employed worldwide to combat poverty.\n", - "\n", - "What all these examples highlight is that innovation serves as the link between recognizing a problem and forging a solution. It is not purely about technological advancements, but rather a mindset of approaching challenges with a can-do spirit, willingness to take risks, and a commitment to continuous improvement.\n", - "\n", - "It is incumbent upon us as global citizens, policymakers, entrepreneurs, and educators to foster an environment that encourages creativity and rewards experimentation. By investing in education, promoting cross-disciplinary collaboration, and ensuring equitable access to resources, we can ensure that the fires of innovation remain ablaze.\n", - "\n", - "It is through this collective commitment to cultivating and harnessing the power of innovation that we stand our best chance of solving the most pressing issues of our time. The road ahead is paved with uncertainty, but with the torch of innovation in hand, we can light the way to a brighter, more resilient tomorrow.\n", - "\n", - "---\n", - "\n", - "This post touches on a ubiquitous theme—innovation—and its essential role in addressing the world’s most pressing challenges. I hope this sample aligns with your interests or inspires a specific direction for your blog post. If you provide me with more information on the desired context, I can further tailor the content to meet your needs.\n", - "\n", - "TERMINATE\n", - "\n", - "--------------------------------------------------------------------------------\n" - ] - }, - { - "data": { - "text/plain": [ - "{: ChatResult(chat_history=[{'content': 'On which days this year was Microsoft Stock higher than $400? Put results in a table.', 'role': 'assistant'}, {'content': \"Microsoft's stock closed above $400 on the following dates in 2024: January 24th ($402.56), January 25th ($404.87), January 26th ($403.93), January 29th ($409.72), January 30th ($408.59), February 1st ($403.78), February 2nd ($411.22), February 5th ($405.65), February 6th ($405.49), and February 7th ($414.05).\", 'role': 'user'}, {'content': '', 'role': 'assistant'}], summary='', cost=({'total_cost': 0}, {'total_cost': 0}), human_input=[]),\n", - " : ChatResult(chat_history=[{'content': 'Investigate the possible reasons of the stock performance.\\nContext: \\n', 'role': 'assistant'}, {'content': 'There has been a miscommunication regarding the term \"Inner-code-interpreter,\" which does not match any known stock ticker symbols or standard commands related to financial assistance or stock performance investigation. To proceed with the analysis of stock performance, the correct stock symbol or company name is required. The user has not provided this information, and thus the financial analysis of a specific stock has not been initiated. Instead, an explanation of how the user can proceed with the analysis upon providing the necessary information has been offered.', 'role': 'user'}, {'content': '', 'role': 'assistant'}], summary='', cost=({'total_cost': 0}, {'total_cost': 0}), human_input=[]),\n", - " : ChatResult(chat_history=[{'content': 'Develop an engaging blog post using any information provided.\\nContext: \\n\\n', 'role': 'assistant'}, {'content': \"I'm happy to create an engaging blog post for you, however, it seems there's been an oversight, and no information or context was provided for the content you're looking for. Could you please provide some details or a topic you're interested in? This could relate to anything from technology, lifestyle, culture, science, personal development, travel, health, and more. Once I have that, I'll craft a compelling narrative for your intended audience.\", 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': \"It appears that your request may have been inadvertently left blank, thus not providing me with a specific theme or topic for developing a blog post. In order to assist you effectively, I need some sort of context or area of interest to focus on.\\n\\nYou may want to consider topics such as emerging technology trends, the impact of social media on communication, the importance of sustainable living practices, or the benefits of mindfulness in today's fast-paced world, among others.\\n\\nPlease provide me with the direction you'd like to take or any specific points you wish to address in your blog post. Once I have this information, I can proceed to craft an engaging and insightful narrative for your audience.\", 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': 'It seems like there might be a communication issue since I haven\\'t received any additional details or context for the blog post. In the spirit of creativity and to provide you with a sample of my work, I will proceed to write an engaging piece on a topic that has universal relevance: \"The Power of Innovation in Solving Global Challenges.\"\\n\\n---\\n\\n**Igniting Change: The Power of Innovation in Solving Global Challenges**\\n\\nIn a world that\\'s smaller and more connected than ever, human ingenuity has emerged as the most potent tool in our arsenal as we confront a host of global challenges. Innovation, the child of necessity and imagination, has the transformative potential to not only reshape our daily lives but also to propel us towards a more sustainable and equitable future.\\n\\nFrom the desalination plants that convert seawater into life-sustaining fresh water to the drones that seed billions of trees in deforested regions, every revolutionary idea began as a mere spark in the human mind. It is this spark that has the power to cross borders, connect cultures, and confront crises—from climate change to global health concerns.\\n\\nTake the example of renewable energy; once a niche and expensive proposition, it now stands at the forefront of our fight against climate change. Innovations in solar and wind power technology have led to significant cost reductions, making them more accessible and feasible as a primary energy source. This shift not only promises cleaner air and a healthier planet but also jobs, economic growth, and a step towards energy independence for nations.\\n\\nAmidst a global health pandemic, we\\'ve witnessed an unprecedented acceleration in vaccine development. This leap forward was rooted in innovative technologies such as mRNA, which have changed the landscape of medicine and offer promising platforms for tackling infectious diseases that have plagued humanity for centuries.\\n\\nHowever, innovation doesn\\'t solely belong to the grandiose scientific breakthroughs that make headlines. It thrives in the quiet corners of daily life, in the processes improved by a factory worker\\'s suggestion, or in the homegrown solutions to local problems that spread globally—like microcredit systems that began in South Asia and are now employed worldwide to combat poverty.\\n\\nWhat all these examples highlight is that innovation serves as the link between recognizing a problem and forging a solution. It is not purely about technological advancements, but rather a mindset of approaching challenges with a can-do spirit, willingness to take risks, and a commitment to continuous improvement.\\n\\nIt is incumbent upon us as global citizens, policymakers, entrepreneurs, and educators to foster an environment that encourages creativity and rewards experimentation. By investing in education, promoting cross-disciplinary collaboration, and ensuring equitable access to resources, we can ensure that the fires of innovation remain ablaze.\\n\\nIt is through this collective commitment to cultivating and harnessing the power of innovation that we stand our best chance of solving the most pressing issues of our time. The road ahead is paved with uncertainty, but with the torch of innovation in hand, we can light the way to a brighter, more resilient tomorrow.\\n\\n---\\n\\nThis post touches on a ubiquitous theme—innovation—and its essential role in addressing the world’s most pressing challenges. I hope this sample aligns with your interests or inspires a specific direction for your blog post. If you provide me with more information on the desired context, I can further tailor the content to meet your needs.\\n\\nTERMINATE', 'role': 'user'}], summary='It seems like there might be a communication issue since I haven\\'t received any additional details or context for the blog post. In the spirit of creativity and to provide you with a sample of my work, I will proceed to write an engaging piece on a topic that has universal relevance: \"The Power of Innovation in Solving Global Challenges.\"\\n\\n---\\n\\n**Igniting Change: The Power of Innovation in Solving Global Challenges**\\n\\nIn a world that\\'s smaller and more connected than ever, human ingenuity has emerged as the most potent tool in our arsenal as we confront a host of global challenges. Innovation, the child of necessity and imagination, has the transformative potential to not only reshape our daily lives but also to propel us towards a more sustainable and equitable future.\\n\\nFrom the desalination plants that convert seawater into life-sustaining fresh water to the drones that seed billions of trees in deforested regions, every revolutionary idea began as a mere spark in the human mind. It is this spark that has the power to cross borders, connect cultures, and confront crises—from climate change to global health concerns.\\n\\nTake the example of renewable energy; once a niche and expensive proposition, it now stands at the forefront of our fight against climate change. Innovations in solar and wind power technology have led to significant cost reductions, making them more accessible and feasible as a primary energy source. This shift not only promises cleaner air and a healthier planet but also jobs, economic growth, and a step towards energy independence for nations.\\n\\nAmidst a global health pandemic, we\\'ve witnessed an unprecedented acceleration in vaccine development. This leap forward was rooted in innovative technologies such as mRNA, which have changed the landscape of medicine and offer promising platforms for tackling infectious diseases that have plagued humanity for centuries.\\n\\nHowever, innovation doesn\\'t solely belong to the grandiose scientific breakthroughs that make headlines. It thrives in the quiet corners of daily life, in the processes improved by a factory worker\\'s suggestion, or in the homegrown solutions to local problems that spread globally—like microcredit systems that began in South Asia and are now employed worldwide to combat poverty.\\n\\nWhat all these examples highlight is that innovation serves as the link between recognizing a problem and forging a solution. It is not purely about technological advancements, but rather a mindset of approaching challenges with a can-do spirit, willingness to take risks, and a commitment to continuous improvement.\\n\\nIt is incumbent upon us as global citizens, policymakers, entrepreneurs, and educators to foster an environment that encourages creativity and rewards experimentation. By investing in education, promoting cross-disciplinary collaboration, and ensuring equitable access to resources, we can ensure that the fires of innovation remain ablaze.\\n\\nIt is through this collective commitment to cultivating and harnessing the power of innovation that we stand our best chance of solving the most pressing issues of our time. The road ahead is paved with uncertainty, but with the torch of innovation in hand, we can light the way to a brighter, more resilient tomorrow.\\n\\n---\\n\\nThis post touches on a ubiquitous theme—innovation—and its essential role in addressing the world’s most pressing challenges. I hope this sample aligns with your interests or inspires a specific direction for your blog post. If you provide me with more information on the desired context, I can further tailor the content to meet your needs.\\n\\n', cost=({'total_cost': 0.06972, 'gpt-4': {'cost': 0.06972, 'prompt_tokens': 542, 'completion_tokens': 891, 'total_tokens': 1433}}, {'total_cost': 0}), human_input=[])}" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "financial_assistant_2 = autogen.AssistantAgent(\n", - " name=\"Financial_assistant_2\",\n", - " llm_config={\"config_list\": config_list},\n", - ")\n", - "writer = autogen.AssistantAgent(\n", - " name=\"Writer\",\n", - " llm_config={\"config_list\": config_list},\n", - " is_termination_msg=lambda x: x.get(\"content\", \"\").find(\"TERMINATE\") >= 0,\n", - " system_message=\"\"\"\n", - " You are a professional writer, known for\n", - " your insightful and engaging articles.\n", - " You transform complex concepts into compelling narratives.\n", - " Reply \"TERMINATE\" in the end when everything is done.\n", - " \"\"\",\n", - ")\n", - "\n", - "financial_assistant_2.register_nested_chats(\n", - " [autogen.Agent, None], [{\"recipient\": manager, \"summary_method\": \"reflection_with_llm\"}]\n", - ")\n", - "user.initiate_chats(\n", - " [\n", - " {\n", - " \"recipient\": financial_assistant_1,\n", - " \"message\": financial_tasks[0],\n", - " \"clear_history\": True,\n", - " \"silent\": False,\n", - " \"takeaway_method\": \"reflection_with_llm\",\n", - " },\n", - " {\n", - " \"recipient\": financial_assistant_2,\n", - " \"message\": financial_tasks[1],\n", - " \"takeaway_method\": \"reflection_with_llm\",\n", - " },\n", - " {\n", - " \"recipient\": writer,\n", - " \"message\": writing_tasks[0],\n", - " },\n", - " ]\n", - ")" - ] } ], "metadata": { From ee1b7b5f5be7d74dc841fdc37694032d70dcca5b Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Thu, 8 Feb 2024 22:43:34 -0500 Subject: [PATCH 07/57] optiguide --- .../agentchat_nestedchat_optiguide_v2.ipynb | 1326 +++++++++++++++++ 1 file changed, 1326 insertions(+) create mode 100644 notebook/agentchat_nestedchat_optiguide_v2.ipynb diff --git a/notebook/agentchat_nestedchat_optiguide_v2.ipynb b/notebook/agentchat_nestedchat_optiguide_v2.ipynb new file mode 100644 index 00000000000..a19100d5324 --- /dev/null +++ b/notebook/agentchat_nestedchat_optiguide_v2.ipynb @@ -0,0 +1,1326 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# OptiGuide with Nested Chats in AutoGen \n", + "\n", + "This is a nested chat re-implementation of OptiGuide referring to: https://github.com/qingyun-wu/autogen-eval/blob/main/application/A4-multi-agent-coding/optiguide_example.ipynb\n", + " \n", + "\n", + "## Requirements\n", + "\n", + "AutoGen requires `Python>=3.8`. To run this notebook example, please install:\n", + "```bash\n", + "pip install pyautogen\n", + "```" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import autogen\n", + "from typing import Union\n", + "\n", + "# test Gurobi installation\n", + "from gurobipy import GRB\n", + "from eventlet.timeout import Timeout\n", + "import re\n", + "from termcolor import colored\n", + "from autogen.code_utils import extract_code\n", + "\n", + "# import auxillary packages\n", + "import requests # for loading the example source code\n", + "\n", + "config_list_gpt4 = autogen.config_list_from_json(\n", + " \"OAI_CONFIG_LIST\",\n", + " # filter_dict={\n", + " # \"model\": [\"gpt-4\", \"gpt4\", \"gpt-4-32k\", \"gpt-4-32k-0314\", \"gpt-4-32k-v0314\"],\n", + " # },\n", + ")\n", + "llm_config = {\"config_list\": config_list_gpt4}" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## OptiGuide with Nested Chat" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### System Messages" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "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", + ".\n", + ".\n", + ".\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": [ + "# %% System Messages\n", + "WRITER_SYSTEM_MSG = \"\"\"You are a chatbot to:\n", + "(1) write Python code to answer users questions for supply chain-related coding\n", + "project;\n", + "(2) explain solutions from a Gurobi/Python solver.\n", + "\n", + "--- SOURCE CODE ---\n", + "{source_code}\n", + "\n", + "--- DOC STR ---\n", + "{doc_str}\n", + "---\n", + "\n", + "Here are some example questions and their answers and codes:\n", + "--- EXAMPLES ---\n", + "{example_qa}\n", + "---\n", + "\n", + "The execution result of the original source code is below.\n", + "--- Original Result ---\n", + "{execution_result}\n", + "\n", + "Note that your written code will be added to the lines with substring:\n", + "\"# OPTIGUIDE *** CODE GOES HERE\"\n", + "So, you don't need to write other code, such as m.optimize() or m.update().\n", + "You just need to write code snippet in ```python ...``` block.\n", + "\"\"\"\n", + "\n", + "SAFEGUARD_SYSTEM_MSG = \"\"\"\n", + "Given the original source code:\n", + "{source_code}\n", + "\n", + "Is the following code safe (not malicious code to break security\n", + "and privacy) to run?\n", + "Answer only one word.\n", + "If not safe, answer `DANGER`; else, answer `SAFE`.\n", + "\"\"\"\n", + "\n", + "# %% Constant strings to match code lines in the source code.\n", + "DATA_CODE_STR = \"# OPTIGUIDE DATA CODE GOES HERE\"\n", + "CONSTRAINT_CODE_STR = \"# OPTIGUIDE CONSTRAINT CODE GOES HERE\"\n", + "\n", + "# In-context learning examples.\n", + "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", + "\"\"\"\n", + "\n", + "CODE_PROMPT = \"\"\"\n", + "Answer Code:\n", + "\"\"\"\n", + "\n", + "DEBUG_PROMPT = \"\"\"\n", + "\n", + "While running the code you suggested, I encountered the {error_type}:\n", + "--- ERROR MESSAGE ---\n", + "{error_message}\n", + "\n", + "Please try to resolve this bug, and rewrite the code snippet.\n", + "--- NEW CODE ---\n", + "\"\"\"\n", + "\n", + "SAFEGUARD_PROMPT = \"\"\"\n", + "--- Code ---\n", + "{code}\n", + "\n", + "--- One-Word Answer: SAFE or DANGER ---\n", + "\"\"\"\n", + "\n", + "INTERPRETER_PROMPT = \"\"\"Here are the execution results: {execution_rst}\n", + "\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\n", + "beginning.\n", + "\n", + "--- HUMAN READABLE ANSWER ---\n", + "\"\"\"\n", + "\n", + "# Get the source code of the coffee example from OptiGuide's official repo\n", + "code_url = \"https://raw.githubusercontent.com/microsoft/OptiGuide/main/benchmark/application/coffee.py\"\n", + "response = requests.get(code_url)\n", + "# Check if the request was successful\n", + "if response.status_code == 200:\n", + " # Get the text content from the response\n", + " code = response.text\n", + "else:\n", + " raise RuntimeError(\"Failed to retrieve the file.\")\n", + "# code = open(code_url, \"r\").read() # for local files\n", + "\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:]))\n", + "\n", + "writer_system_msg = WRITER_SYSTEM_MSG.format(\n", + " source_code=code,\n", + " doc_str=\"\",\n", + " example_qa=example_qa,\n", + " execution_result=\"\",\n", + ")\n", + "safeguard_system_msg = SAFEGUARD_SYSTEM_MSG.format(source_code=code)\n", + "# TODO: system message needs to be changed. Needs to add execution result of the original source code and user_chat_history\n", + "\n", + "\n", + "def replace(src_code: str, old_code: str, new_code: str) -> str:\n", + " \"\"\"\n", + " Inserts new code into the source code by replacing a specified old\n", + " code block.\n", + "\n", + " Args:\n", + " src_code (str): The source code to modify.\n", + " old_code (str): The code block to be replaced.\n", + " new_code (str): The new code block to insert.\n", + "\n", + " Returns:\n", + " str: The modified source code with the new code inserted.\n", + "\n", + " Raises:\n", + " None\n", + "\n", + " Example:\n", + " src_code = 'def hello_world():\\n print(\"Hello, world!\")\\n\\n# Some\n", + " other code here'\n", + " old_code = 'print(\"Hello, world!\")'\n", + " new_code = 'print(\"Bonjour, monde!\")\\nprint(\"Hola, mundo!\")'\n", + " modified_code = _replace(src_code, old_code, new_code)\n", + " print(modified_code)\n", + " # Output:\n", + " # def hello_world():\n", + " # print(\"Bonjour, monde!\")\n", + " # print(\"Hola, mundo!\")\n", + " # Some other code here\n", + " \"\"\"\n", + " pattern = r\"( *){old_code}\".format(old_code=old_code)\n", + " head_spaces = re.search(pattern, src_code, flags=re.DOTALL).group(1)\n", + " new_code = \"\\n\".join([head_spaces + line for line in new_code.split(\"\\n\")])\n", + " rst = re.sub(pattern, new_code, src_code)\n", + " return rst\n", + "\n", + "\n", + "def insert_code(src_code: str, new_lines: str) -> str:\n", + " \"\"\"insert a code patch into the source code.\n", + "\n", + "\n", + " Args:\n", + " src_code (str): the full source code\n", + " new_lines (str): The new code.\n", + "\n", + " Returns:\n", + " str: the full source code after insertion (replacement).\n", + " \"\"\"\n", + " if new_lines.find(\"addConstr\") >= 0:\n", + " return replace(src_code, CONSTRAINT_CODE_STR, new_lines)\n", + " else:\n", + " return replace(src_code, DATA_CODE_STR, new_lines)\n", + "\n", + "\n", + "def run_with_exec(src_code: str) -> Union[str, Exception]:\n", + " \"\"\"Run the code snippet with exec.\n", + "\n", + " Args:\n", + " src_code (str): The source code to run.\n", + "\n", + " Returns:\n", + " object: The result of the code snippet.\n", + " If the code succeed, returns the objective value (float or string).\n", + " else, return the error (exception)\n", + " \"\"\"\n", + " locals_dict = {}\n", + " locals_dict.update(globals())\n", + " locals_dict.update(locals())\n", + "\n", + " timeout = Timeout(\n", + " 60,\n", + " TimeoutError(\"This is a timeout exception, in case \" \"GPT's code falls into infinite loop.\"),\n", + " )\n", + " try:\n", + " exec(src_code, locals_dict, locals_dict)\n", + " except Exception as e:\n", + " return e\n", + " finally:\n", + " timeout.cancel()\n", + "\n", + " try:\n", + " status = locals_dict[\"m\"].Status\n", + " if status != GRB.OPTIMAL:\n", + " if status == GRB.UNBOUNDED:\n", + " ans = \"unbounded\"\n", + " elif status == GRB.INF_OR_UNBD:\n", + " ans = \"inf_or_unbound\"\n", + " elif status == GRB.INFEASIBLE:\n", + " ans = \"infeasible\"\n", + " m = locals_dict[\"m\"]\n", + " m.computeIIS()\n", + " constrs = [c.ConstrName for c in m.getConstrs() if c.IISConstr]\n", + " ans += \"\\nConflicting Constraints:\\n\" + str(constrs)\n", + " else:\n", + " ans = \"Model Status:\" + str(status)\n", + " else:\n", + " ans = \"Optimization problem solved. The objective value is: \" + str(locals_dict[\"m\"].objVal)\n", + " except Exception as e:\n", + " return e\n", + "\n", + " return ans" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Define Agents" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "class OptiGuide(autogen.AssistantAgent):\n", + " source_code: str = code\n", + " debug_times: int = 3\n", + " debug_times_left: int = 3\n", + " example_qa: str = \"\"\n", + " success: bool = False\n", + " user_chat_history: str = \"\"\n", + "\n", + " def set_success(self, success: bool):\n", + " self.success = success\n", + "\n", + " @property\n", + " def get_success(self):\n", + " return self.success\n", + "\n", + " def update_debug_times(self):\n", + " self.debug_times_left -= 1\n", + "\n", + " def set_user_chat_history(self, user_chat_history: str):\n", + " self.user_chat_history = user_chat_history\n", + "\n", + "\n", + "class Writer(autogen.AssistantAgent):\n", + " source_code: str = code\n", + " debug_times: int = 3\n", + " debug_times_left: int = 3\n", + " example_qa: str = \"\"\n", + " user_chat_history: str = \"\"\n", + "\n", + " def set_user_chat_history(self, user_chat_history: str):\n", + " self.user_chat_history = user_chat_history\n", + "\n", + "\n", + "writer = Writer(\"writer\", llm_config=llm_config)\n", + "safeguard = autogen.AssistantAgent(\"safeguard\", llm_config=llm_config)\n", + "optiguide_commander = OptiGuide(\"commander\", llm_config=llm_config)\n", + "\n", + "user = autogen.UserProxyAgent(\n", + " \"user\", max_consecutive_auto_reply=0, human_input_mode=\"NEVER\", code_execution_config=False\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Register reply func and nested chats" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "def nested_writer_reply(chat_queue, recipient, messages, sender, config):\n", + " # max_turn = 10\n", + " # if chat_queue[0][\"recipient\"].reply_num >= max_turn:\n", + " # return True, None\n", + " msg_content = messages[-1].get(\"content\", \"\")\n", + " # board = config\n", + " # get execution result of the original source code\n", + " sender_history = recipient.chat_messages[sender]\n", + " user_chat_history = \"\\nHere are the history of discussions:\\n\" f\"{sender_history}\"\n", + "\n", + " if sender.name == \"user\":\n", + " execution_result = msg_content # TODO: get the execution result of the original source code\n", + " else:\n", + " execution_result = \"\"\n", + " writer_sys_msg = (\n", + " WRITER_SYSTEM_MSG.format(\n", + " source_code=recipient.source_code,\n", + " doc_str=\"\",\n", + " example_qa=example_qa,\n", + " execution_result=execution_result,\n", + " )\n", + " + user_chat_history\n", + " )\n", + " # safeguard_sys_msg = (\n", + " # SAFEGUARD_SYSTEM_MSG.format(source_code=recipient.source_code)\n", + " # + user_chat_history\n", + " # )\n", + " # update system message\n", + " nested_agent = chat_queue[0][\"recipient\"]\n", + " nested_agent.update_system_message(writer_sys_msg)\n", + " nested_agent.set_user_chat_history(user_chat_history)\n", + " # safeguard.update_system_message(safeguard_sys_msg) #TODO: update system message for safeguard\n", + " nested_agent.reset()\n", + " # safeguard.reset() #TODO: reset safeguard\n", + " recipient.debug_times_left = recipient.debug_times\n", + " recipient.set_success(False)\n", + "\n", + " chat_queue[0][\"recipient\"] = nested_agent\n", + " chat_queue[0][\"message\"] = CODE_PROMPT\n", + "\n", + " chat_res = recipient.initiate_chats(chat_queue)\n", + "\n", + " if recipient.success:\n", + " reply = list(chat_res.values())[-1].summary\n", + " else:\n", + " reply = \"Sorry. I cannot answer your question.\"\n", + " return True, reply\n", + "\n", + "\n", + "def nested_safeguard_reply(chat_queue, recipient, messages, sender, config):\n", + " if recipient.success:\n", + " # no reply to writer\n", + " return True, None\n", + " # Step 3: safeguard\n", + "\n", + " safeguard_sys_msg = SAFEGUARD_SYSTEM_MSG.format(source_code=recipient.source_code) + recipient.user_chat_history\n", + "\n", + " chat_agent = chat_queue[0][\"recipient\"]\n", + " chat_agent.update_system_message(safeguard_sys_msg)\n", + " chat_agent.reset()\n", + " chat_queue[0][\"recipient\"] = chat_agent\n", + " # chat_agent[0][\"recipient\"].set_success(False)\n", + " last_msg_content = messages[-1].get(\"content\", \"\")\n", + " _, code = extract_code(last_msg_content)[0]\n", + " chat_info = chat_queue[0]\n", + " chat_info[\"message\"] = SAFEGUARD_SYSTEM_MSG.format(source_code=recipient.source_code)\n", + " chat_queue[0] = chat_info\n", + " res = recipient.initiate_chats(chat_queue)\n", + "\n", + " safe_msg = list(res.values())[-1].summary\n", + " if safe_msg.find(\"DANGER\") < 0:\n", + " # Step 4 and 5: Run the code and obtain the results\n", + " src_code = insert_code(recipient.source_code, code)\n", + " execution_rst = run_with_exec(src_code)\n", + " print(colored(str(execution_rst), \"yellow\"))\n", + " if type(execution_rst) in [str, int, float]:\n", + " # we successfully run the code and get the result\n", + " recipient.set_success(True)\n", + " # Step 6: request to interpret results\n", + " return True, INTERPRETER_PROMPT.format(execution_rst=execution_rst)\n", + " else:\n", + " # DANGER: If not safe, try to debug. Redo coding\n", + " execution_rst = \"\"\"\n", + " Sorry, this new code is not safe to run. I would not allow you to execute it.\n", + " Please try to find a new way (coding) to answer the question.\"\"\"\n", + " if recipient.debug_times_left > 0:\n", + " # Try to debug and write code again (back to step 2)\n", + " recipient.update_debug_times()\n", + " return True, DEBUG_PROMPT.format(error_type=type(execution_rst), error_message=str(execution_rst))\n", + "\n", + "\n", + "writer_chat_queue = [{\"recipient\": writer, \"summary_method\": \"last_msg\"}]\n", + "safeguard_chat_queue = [{\"recipient\": safeguard, \"summary_method\": \"last_msg\"}]\n", + "# writer is triggered only when receiving a message from the user\n", + "optiguide_commander.register_nested_chats(\"user\", writer_chat_queue, nested_writer_reply)\n", + "# safeguard is triggered only when receiving a message from the writer\n", + "optiguide_commander.register_nested_chats(\"writer\", safeguard_chat_queue, nested_safeguard_reply)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Let agents talk" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33muser\u001b[0m (to commander):\n", + "\n", + "What if we prohibit shipping from supplier 1 to roastery 2?\n", + "\n", + "--------------------------------------------------------------------------------\n", + "nestss writer\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "\n", + "Answer Code:\n", + "\n", + "\n", + "With the following carryover: \n", + "\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mcommander\u001b[0m (to writer):\n", + "\n", + "\n", + "Answer Code:\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mwriter\u001b[0m (to commander):\n", + "\n", + "```python\n", + "model.addConstr(x['supplier1', 'roastery2'] == 0, \"prohibit_s1_r2\")\n", + "```\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "\n", + "Given the original source code:\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", + "# Create a new model\n", + "model = Model(\"coffee_distribution\")\n", + "\n", + "# OPTIGUIDE DATA CODE GOES HERE\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] 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] 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] 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] for j in shipping_cost_from_roastery_to_cafe.keys()\n", + " if j[1] == c) >= dark_coffee_needed_for_cafe[c],\n", + " f\"dark_demand_{c}\")\n", + "\n", + "# Optimize model\n", + "model.optimize()\n", + "m = model\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", + "\n", + "Is the following code safe (not malicious code to break security\n", + "and privacy) to run?\n", + "Answer only one word.\n", + "If not safe, answer `DANGER`; else, answer `SAFE`.\n", + "\n", + "\n", + "With the following carryover: \n", + "\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mcommander\u001b[0m (to safeguard):\n", + "\n", + "\n", + "Given the original source code:\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", + "# Create a new model\n", + "model = Model(\"coffee_distribution\")\n", + "\n", + "# OPTIGUIDE DATA CODE GOES HERE\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] 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] 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] 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] for j in shipping_cost_from_roastery_to_cafe.keys()\n", + " if j[1] == c) >= dark_coffee_needed_for_cafe[c],\n", + " f\"dark_demand_{c}\")\n", + "\n", + "# Optimize model\n", + "model.optimize()\n", + "m = model\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", + "\n", + "Is the following code safe (not malicious code to break security\n", + "and privacy) to run?\n", + "Answer only one word.\n", + "If not safe, answer `DANGER`; else, answer `SAFE`.\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33msafeguard\u001b[0m (to commander):\n", + "\n", + "SAFE\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mcommander\u001b[0m (to safeguard):\n", + "\n", + "TERMINATE\n", + "\n", + "--------------------------------------------------------------------------------\n", + "Restricted license - for non-production use only - expires 2025-11-24\n", + "Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n", + "\n", + "CPU model: Apple M3 Max\n", + "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", + "\n", + "Optimize a model with 11 rows, 18 columns and 36 nonzeros\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 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.00 seconds (0.00 work units)\n", + "Thread count was 14 (of 14 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 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n", + "\n", + "CPU model: Apple M3 Max\n", + "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", + "\n", + "Optimize a model with 12 rows, 18 columns and 37 nonzeros\n", + "Model fingerprint: 0xb1400c60\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 prohibit_s1_r2 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.00 seconds (0.00 work units)\n", + "Thread count was 14 (of 14 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", + "Thu Feb 8 22:41:52 2024\n", + "Optimal cost: 2760.0\n", + "\u001b[33mOptimization problem solved. The objective value is: 2760.0\u001b[0m\n", + "\u001b[33mcommander\u001b[0m (to writer):\n", + "\n", + "Here are the execution results: Optimization problem solved. The objective value is: 2760.0\n", + "\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\n", + "beginning.\n", + "\n", + "--- HUMAN READABLE ANSWER ---\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mwriter\u001b[0m (to commander):\n", + "\n", + "Certainly, the results from optimizing the supply chain model suggest the following:\n", + "\n", + "After adding the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was still successfully solved. The new objective value, which represents the total cost of the supply chain operation after implementing the constraint, is $2760.0.\n", + "\n", + "To provide a comparison with the original results, we should look at the objective value before the constraint was added. If the previous objective value is lower than $2760.0, this implies that the new constraint increased the total cost of operations, highlighting the economic impact of not using supplier 1 for roastery 2.\n", + "\n", + "On the other hand, if the previous objective value is higher than $2760.0, then despite the constraint, we have found a more cost-effective way to meet the supply and demand needs.\n", + "\n", + "Unfortunately, the original objective value prior to this constraint being added is not provided here. For a full comparison, you would need that original value to assess the financial impact of this constraint on the overall supply chain cost.\n", + "\n", + "--- HUMAN READABLE ANSWER ---\n", + "The new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mcommander\u001b[0m (to user):\n", + "\n", + "Certainly, the results from optimizing the supply chain model suggest the following:\n", + "\n", + "After adding the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was still successfully solved. The new objective value, which represents the total cost of the supply chain operation after implementing the constraint, is $2760.0.\n", + "\n", + "To provide a comparison with the original results, we should look at the objective value before the constraint was added. If the previous objective value is lower than $2760.0, this implies that the new constraint increased the total cost of operations, highlighting the economic impact of not using supplier 1 for roastery 2.\n", + "\n", + "On the other hand, if the previous objective value is higher than $2760.0, then despite the constraint, we have found a more cost-effective way to meet the supply and demand needs.\n", + "\n", + "Unfortunately, the original objective value prior to this constraint being added is not provided here. For a full comparison, you would need that original value to assess the financial impact of this constraint on the overall supply chain cost.\n", + "\n", + "--- HUMAN READABLE ANSWER ---\n", + "The new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "data": { + "text/plain": [ + "ChatResult(chat_history=[{'content': 'What if we prohibit shipping from supplier 1 to roastery 2?', 'role': 'assistant'}, {'content': 'Certainly, the results from optimizing the supply chain model suggest the following:\\n\\nAfter adding the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was still successfully solved. The new objective value, which represents the total cost of the supply chain operation after implementing the constraint, is $2760.0.\\n\\nTo provide a comparison with the original results, we should look at the objective value before the constraint was added. If the previous objective value is lower than $2760.0, this implies that the new constraint increased the total cost of operations, highlighting the economic impact of not using supplier 1 for roastery 2.\\n\\nOn the other hand, if the previous objective value is higher than $2760.0, then despite the constraint, we have found a more cost-effective way to meet the supply and demand needs.\\n\\nUnfortunately, the original objective value prior to this constraint being added is not provided here. For a full comparison, you would need that original value to assess the financial impact of this constraint on the overall supply chain cost.\\n\\n--- HUMAN READABLE ANSWER ---\\nThe new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.', 'role': 'user'}], summary='Certainly, the results from optimizing the supply chain model suggest the following:\\n\\nAfter adding the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was still successfully solved. The new objective value, which represents the total cost of the supply chain operation after implementing the constraint, is $2760.0.\\n\\nTo provide a comparison with the original results, we should look at the objective value before the constraint was added. If the previous objective value is lower than $2760.0, this implies that the new constraint increased the total cost of operations, highlighting the economic impact of not using supplier 1 for roastery 2.\\n\\nOn the other hand, if the previous objective value is higher than $2760.0, then despite the constraint, we have found a more cost-effective way to meet the supply and demand needs.\\n\\nUnfortunately, the original objective value prior to this constraint being added is not provided here. For a full comparison, you would need that original value to assess the financial impact of this constraint on the overall supply chain cost.\\n\\n--- HUMAN READABLE ANSWER ---\\nThe new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.', cost=({'total_cost': 0.0465, 'gpt-4': {'cost': 0.0465, 'prompt_tokens': 1544, 'completion_tokens': 3, 'total_tokens': 1547}}, {'total_cost': 0}), human_input=[])" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "user.initiate_chat(optiguide_commander, message=\"What if we prohibit shipping from supplier 1 to roastery 2?\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.10.13" + }, + "vscode": { + "interpreter": { + "hash": "949777d72b0d2535278d3dc13498b2535136f6dfe0678499012e853ee9abcab1" + } + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": { + "2d910cfd2d2a4fc49fc30fbbdc5576a7": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "454146d0f7224f038689031002906e6f": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_e4ae2b6f5a974fd4bafb6abb9d12ff26", + "IPY_MODEL_577e1e3cc4db4942b0883577b3b52755", + "IPY_MODEL_b40bdfb1ac1d4cffb7cefcb870c64d45" + ], + "layout": "IPY_MODEL_dc83c7bff2f241309537a8119dfc7555", + "tabbable": null, + "tooltip": null + } + }, + "577e1e3cc4db4942b0883577b3b52755": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_2d910cfd2d2a4fc49fc30fbbdc5576a7", + "max": 1, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_74a6ba0c3cbc4051be0a83e152fe1e62", + "tabbable": null, + "tooltip": null, + "value": 1 + } + }, + "6086462a12d54bafa59d3c4566f06cb2": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "74a6ba0c3cbc4051be0a83e152fe1e62": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "7d3f3d9e15894d05a4d188ff4f466554": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "background": null, + "description_width": "", + "font_size": null, + "text_color": null + } + }, + "b40bdfb1ac1d4cffb7cefcb870c64d45": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HTMLView", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_f1355871cc6f4dd4b50d9df5af20e5c8", + "placeholder": "​", + "style": "IPY_MODEL_ca245376fd9f4354af6b2befe4af4466", + "tabbable": null, + "tooltip": null, + "value": " 1/1 [00:00<00:00, 44.69it/s]" + } + }, + "ca245376fd9f4354af6b2befe4af4466": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "background": null, + "description_width": "", + "font_size": null, + "text_color": null + } + }, + "dc83c7bff2f241309537a8119dfc7555": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "e4ae2b6f5a974fd4bafb6abb9d12ff26": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HTMLView", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_6086462a12d54bafa59d3c4566f06cb2", + "placeholder": "​", + "style": "IPY_MODEL_7d3f3d9e15894d05a4d188ff4f466554", + "tabbable": null, + "tooltip": null, + "value": "100%" + } + }, + "f1355871cc6f4dd4b50d9df5af20e5c8": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + } + }, + "version_major": 2, + "version_minor": 0 + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 40ee04f57739fac1c7a2a522bca87c53d3b55077 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Thu, 8 Feb 2024 22:46:01 -0500 Subject: [PATCH 08/57] rename --- notebook/agentchat_nestedchat_optiguide.ipynb | 48 ++++++++++++------- ...ntchat_nestedchat_optiguide_summary.ipynb} | 48 +++++++------------ 2 files changed, 48 insertions(+), 48 deletions(-) rename notebook/{agentchat_nestedchat_optiguide_v2.ipynb => agentchat_nestedchat_optiguide_summary.ipynb} (89%) diff --git a/notebook/agentchat_nestedchat_optiguide.ipynb b/notebook/agentchat_nestedchat_optiguide.ipynb index e503a6b0a45..a19100d5324 100644 --- a/notebook/agentchat_nestedchat_optiguide.ipynb +++ b/notebook/agentchat_nestedchat_optiguide.ipynb @@ -24,7 +24,7 @@ }, { "cell_type": "code", - "execution_count": 65, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -67,7 +67,7 @@ }, { "cell_type": "code", - "execution_count": 66, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -333,7 +333,7 @@ }, { "cell_type": "code", - "execution_count": 67, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -388,7 +388,7 @@ }, { "cell_type": "code", - "execution_count": 68, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -422,7 +422,6 @@ " # update system message\n", " nested_agent = chat_queue[0][\"recipient\"]\n", " nested_agent.update_system_message(writer_sys_msg)\n", - " print(\"nestss\", nested_agent.name)\n", " nested_agent.set_user_chat_history(user_chat_history)\n", " # safeguard.update_system_message(safeguard_sys_msg) #TODO: update system message for safeguard\n", " nested_agent.reset()\n", @@ -484,7 +483,7 @@ " return True, DEBUG_PROMPT.format(error_type=type(execution_rst), error_message=str(execution_rst))\n", "\n", "\n", - "writer_chat_queue = [{\"recipient\": writer, \"summary_method\": \"reflection_with_llm\"}]\n", + "writer_chat_queue = [{\"recipient\": writer, \"summary_method\": \"last_msg\"}]\n", "safeguard_chat_queue = [{\"recipient\": safeguard, \"summary_method\": \"last_msg\"}]\n", "# writer is triggered only when receiving a message from the user\n", "optiguide_commander.register_nested_chats(\"user\", writer_chat_queue, nested_writer_reply)\n", @@ -501,7 +500,7 @@ }, { "cell_type": "code", - "execution_count": 69, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -793,24 +792,25 @@ "TERMINATE\n", "\n", "--------------------------------------------------------------------------------\n", + "Restricted license - for non-production use only - expires 2025-11-24\n", "Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n", "\n", "CPU model: Apple M3 Max\n", "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0xab8d681d\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", @@ -822,7 +822,7 @@ "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 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", @@ -832,7 +832,7 @@ "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 12 rows, 18 columns and 37 nonzeros\n", - "Model fingerprint: 0x5dab87a5\n", + "Model fingerprint: 0xb1400c60\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", @@ -843,11 +843,12 @@ "MIP start from previous solve did not produce a new incumbent solution\n", "MIP start from previous solve violates constraint prohibit_s1_r2 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", @@ -859,11 +860,11 @@ "Explored 1 nodes (10 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 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", - "Thu Feb 8 22:01:13 2024\n", + "Thu Feb 8 22:41:52 2024\n", "Optimal cost: 2760.0\n", "\u001b[33mOptimization problem solved. The objective value is: 2760.0\u001b[0m\n", "\u001b[33mcommander\u001b[0m (to writer):\n", @@ -896,7 +897,18 @@ "--------------------------------------------------------------------------------\n", "\u001b[33mcommander\u001b[0m (to user):\n", "\n", - "The optimization of the supply chain model with a constraint that prohibits shipping from supplier 1 to roastery 2 was successful, and the calculated total operation cost is $2760.0. To determine the impact of this constraint, a comparison with the original objective value is needed; however, it was not provided in the conversation. If the original value is lower, the constraint has increased costs; if it is higher, the supply chain might be more cost-effective despite the constraint.\n", + "Certainly, the results from optimizing the supply chain model suggest the following:\n", + "\n", + "After adding the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was still successfully solved. The new objective value, which represents the total cost of the supply chain operation after implementing the constraint, is $2760.0.\n", + "\n", + "To provide a comparison with the original results, we should look at the objective value before the constraint was added. If the previous objective value is lower than $2760.0, this implies that the new constraint increased the total cost of operations, highlighting the economic impact of not using supplier 1 for roastery 2.\n", + "\n", + "On the other hand, if the previous objective value is higher than $2760.0, then despite the constraint, we have found a more cost-effective way to meet the supply and demand needs.\n", + "\n", + "Unfortunately, the original objective value prior to this constraint being added is not provided here. For a full comparison, you would need that original value to assess the financial impact of this constraint on the overall supply chain cost.\n", + "\n", + "--- HUMAN READABLE ANSWER ---\n", + "The new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.\n", "\n", "--------------------------------------------------------------------------------\n" ] @@ -904,10 +916,10 @@ { "data": { "text/plain": [ - "ChatResult(chat_history=[{'content': 'What if we prohibit shipping from supplier 1 to roastery 2?', 'role': 'assistant'}, {'content': 'The optimization of the supply chain model with a constraint that prohibits shipping from supplier 1 to roastery 2 was successful, and the calculated total operation cost is $2760.0. To determine the impact of this constraint, a comparison with the original objective value is needed; however, it was not provided in the conversation. If the original value is lower, the constraint has increased costs; if it is higher, the supply chain might be more cost-effective despite the constraint.', 'role': 'user'}], summary='The optimization of the supply chain model with a constraint that prohibits shipping from supplier 1 to roastery 2 was successful, and the calculated total operation cost is $2760.0. To determine the impact of this constraint, a comparison with the original objective value is needed; however, it was not provided in the conversation. If the original value is lower, the constraint has increased costs; if it is higher, the supply chain might be more cost-effective despite the constraint.', cost=({'total_cost': 0.0465, 'gpt-4': {'cost': 0.0465, 'prompt_tokens': 1544, 'completion_tokens': 3, 'total_tokens': 1547}}, {'total_cost': 0}), human_input=[])" + "ChatResult(chat_history=[{'content': 'What if we prohibit shipping from supplier 1 to roastery 2?', 'role': 'assistant'}, {'content': 'Certainly, the results from optimizing the supply chain model suggest the following:\\n\\nAfter adding the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was still successfully solved. The new objective value, which represents the total cost of the supply chain operation after implementing the constraint, is $2760.0.\\n\\nTo provide a comparison with the original results, we should look at the objective value before the constraint was added. If the previous objective value is lower than $2760.0, this implies that the new constraint increased the total cost of operations, highlighting the economic impact of not using supplier 1 for roastery 2.\\n\\nOn the other hand, if the previous objective value is higher than $2760.0, then despite the constraint, we have found a more cost-effective way to meet the supply and demand needs.\\n\\nUnfortunately, the original objective value prior to this constraint being added is not provided here. For a full comparison, you would need that original value to assess the financial impact of this constraint on the overall supply chain cost.\\n\\n--- HUMAN READABLE ANSWER ---\\nThe new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.', 'role': 'user'}], summary='Certainly, the results from optimizing the supply chain model suggest the following:\\n\\nAfter adding the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was still successfully solved. The new objective value, which represents the total cost of the supply chain operation after implementing the constraint, is $2760.0.\\n\\nTo provide a comparison with the original results, we should look at the objective value before the constraint was added. If the previous objective value is lower than $2760.0, this implies that the new constraint increased the total cost of operations, highlighting the economic impact of not using supplier 1 for roastery 2.\\n\\nOn the other hand, if the previous objective value is higher than $2760.0, then despite the constraint, we have found a more cost-effective way to meet the supply and demand needs.\\n\\nUnfortunately, the original objective value prior to this constraint being added is not provided here. For a full comparison, you would need that original value to assess the financial impact of this constraint on the overall supply chain cost.\\n\\n--- HUMAN READABLE ANSWER ---\\nThe new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.', cost=({'total_cost': 0.0465, 'gpt-4': {'cost': 0.0465, 'prompt_tokens': 1544, 'completion_tokens': 3, 'total_tokens': 1547}}, {'total_cost': 0}), human_input=[])" ] }, - "execution_count": 69, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } diff --git a/notebook/agentchat_nestedchat_optiguide_v2.ipynb b/notebook/agentchat_nestedchat_optiguide_summary.ipynb similarity index 89% rename from notebook/agentchat_nestedchat_optiguide_v2.ipynb rename to notebook/agentchat_nestedchat_optiguide_summary.ipynb index a19100d5324..e503a6b0a45 100644 --- a/notebook/agentchat_nestedchat_optiguide_v2.ipynb +++ b/notebook/agentchat_nestedchat_optiguide_summary.ipynb @@ -24,7 +24,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 65, "metadata": {}, "outputs": [], "source": [ @@ -67,7 +67,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 66, "metadata": {}, "outputs": [ { @@ -333,7 +333,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 67, "metadata": {}, "outputs": [], "source": [ @@ -388,7 +388,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 68, "metadata": {}, "outputs": [], "source": [ @@ -422,6 +422,7 @@ " # update system message\n", " nested_agent = chat_queue[0][\"recipient\"]\n", " nested_agent.update_system_message(writer_sys_msg)\n", + " print(\"nestss\", nested_agent.name)\n", " nested_agent.set_user_chat_history(user_chat_history)\n", " # safeguard.update_system_message(safeguard_sys_msg) #TODO: update system message for safeguard\n", " nested_agent.reset()\n", @@ -483,7 +484,7 @@ " return True, DEBUG_PROMPT.format(error_type=type(execution_rst), error_message=str(execution_rst))\n", "\n", "\n", - "writer_chat_queue = [{\"recipient\": writer, \"summary_method\": \"last_msg\"}]\n", + "writer_chat_queue = [{\"recipient\": writer, \"summary_method\": \"reflection_with_llm\"}]\n", "safeguard_chat_queue = [{\"recipient\": safeguard, \"summary_method\": \"last_msg\"}]\n", "# writer is triggered only when receiving a message from the user\n", "optiguide_commander.register_nested_chats(\"user\", writer_chat_queue, nested_writer_reply)\n", @@ -500,7 +501,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 69, "metadata": {}, "outputs": [ { @@ -792,25 +793,24 @@ "TERMINATE\n", "\n", "--------------------------------------------------------------------------------\n", - "Restricted license - for non-production use only - expires 2025-11-24\n", "Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n", "\n", "CPU model: Apple M3 Max\n", "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0xb1e18c0e\n", + "Model fingerprint: 0xab8d681d\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", @@ -822,7 +822,7 @@ "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 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", @@ -832,7 +832,7 @@ "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 12 rows, 18 columns and 37 nonzeros\n", - "Model fingerprint: 0xb1400c60\n", + "Model fingerprint: 0x5dab87a5\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", @@ -843,12 +843,11 @@ "MIP start from previous solve did not produce a new incumbent solution\n", "MIP start from previous solve violates constraint prohibit_s1_r2 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", @@ -860,11 +859,11 @@ "Explored 1 nodes (10 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 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", - "Thu Feb 8 22:41:52 2024\n", + "Thu Feb 8 22:01:13 2024\n", "Optimal cost: 2760.0\n", "\u001b[33mOptimization problem solved. The objective value is: 2760.0\u001b[0m\n", "\u001b[33mcommander\u001b[0m (to writer):\n", @@ -897,18 +896,7 @@ "--------------------------------------------------------------------------------\n", "\u001b[33mcommander\u001b[0m (to user):\n", "\n", - "Certainly, the results from optimizing the supply chain model suggest the following:\n", - "\n", - "After adding the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was still successfully solved. The new objective value, which represents the total cost of the supply chain operation after implementing the constraint, is $2760.0.\n", - "\n", - "To provide a comparison with the original results, we should look at the objective value before the constraint was added. If the previous objective value is lower than $2760.0, this implies that the new constraint increased the total cost of operations, highlighting the economic impact of not using supplier 1 for roastery 2.\n", - "\n", - "On the other hand, if the previous objective value is higher than $2760.0, then despite the constraint, we have found a more cost-effective way to meet the supply and demand needs.\n", - "\n", - "Unfortunately, the original objective value prior to this constraint being added is not provided here. For a full comparison, you would need that original value to assess the financial impact of this constraint on the overall supply chain cost.\n", - "\n", - "--- HUMAN READABLE ANSWER ---\n", - "The new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.\n", + "The optimization of the supply chain model with a constraint that prohibits shipping from supplier 1 to roastery 2 was successful, and the calculated total operation cost is $2760.0. To determine the impact of this constraint, a comparison with the original objective value is needed; however, it was not provided in the conversation. If the original value is lower, the constraint has increased costs; if it is higher, the supply chain might be more cost-effective despite the constraint.\n", "\n", "--------------------------------------------------------------------------------\n" ] @@ -916,10 +904,10 @@ { "data": { "text/plain": [ - "ChatResult(chat_history=[{'content': 'What if we prohibit shipping from supplier 1 to roastery 2?', 'role': 'assistant'}, {'content': 'Certainly, the results from optimizing the supply chain model suggest the following:\\n\\nAfter adding the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was still successfully solved. The new objective value, which represents the total cost of the supply chain operation after implementing the constraint, is $2760.0.\\n\\nTo provide a comparison with the original results, we should look at the objective value before the constraint was added. If the previous objective value is lower than $2760.0, this implies that the new constraint increased the total cost of operations, highlighting the economic impact of not using supplier 1 for roastery 2.\\n\\nOn the other hand, if the previous objective value is higher than $2760.0, then despite the constraint, we have found a more cost-effective way to meet the supply and demand needs.\\n\\nUnfortunately, the original objective value prior to this constraint being added is not provided here. For a full comparison, you would need that original value to assess the financial impact of this constraint on the overall supply chain cost.\\n\\n--- HUMAN READABLE ANSWER ---\\nThe new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.', 'role': 'user'}], summary='Certainly, the results from optimizing the supply chain model suggest the following:\\n\\nAfter adding the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was still successfully solved. The new objective value, which represents the total cost of the supply chain operation after implementing the constraint, is $2760.0.\\n\\nTo provide a comparison with the original results, we should look at the objective value before the constraint was added. If the previous objective value is lower than $2760.0, this implies that the new constraint increased the total cost of operations, highlighting the economic impact of not using supplier 1 for roastery 2.\\n\\nOn the other hand, if the previous objective value is higher than $2760.0, then despite the constraint, we have found a more cost-effective way to meet the supply and demand needs.\\n\\nUnfortunately, the original objective value prior to this constraint being added is not provided here. For a full comparison, you would need that original value to assess the financial impact of this constraint on the overall supply chain cost.\\n\\n--- HUMAN READABLE ANSWER ---\\nThe new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.', cost=({'total_cost': 0.0465, 'gpt-4': {'cost': 0.0465, 'prompt_tokens': 1544, 'completion_tokens': 3, 'total_tokens': 1547}}, {'total_cost': 0}), human_input=[])" + "ChatResult(chat_history=[{'content': 'What if we prohibit shipping from supplier 1 to roastery 2?', 'role': 'assistant'}, {'content': 'The optimization of the supply chain model with a constraint that prohibits shipping from supplier 1 to roastery 2 was successful, and the calculated total operation cost is $2760.0. To determine the impact of this constraint, a comparison with the original objective value is needed; however, it was not provided in the conversation. If the original value is lower, the constraint has increased costs; if it is higher, the supply chain might be more cost-effective despite the constraint.', 'role': 'user'}], summary='The optimization of the supply chain model with a constraint that prohibits shipping from supplier 1 to roastery 2 was successful, and the calculated total operation cost is $2760.0. To determine the impact of this constraint, a comparison with the original objective value is needed; however, it was not provided in the conversation. If the original value is lower, the constraint has increased costs; if it is higher, the supply chain might be more cost-effective despite the constraint.', cost=({'total_cost': 0.0465, 'gpt-4': {'cost': 0.0465, 'prompt_tokens': 1544, 'completion_tokens': 3, 'total_tokens': 1547}}, {'total_cost': 0}), human_input=[])" ] }, - "execution_count": 5, + "execution_count": 69, "metadata": {}, "output_type": "execute_result" } From 4e3c7d054ce82b461f24db1097c4ab0719ffc124 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Fri, 9 Feb 2024 23:21:39 -0500 Subject: [PATCH 09/57] add implementation --- autogen/agent_utils.py | 7 - autogen/agentchat/conversable_agent.py | 71 ++++++- test/agentchat/test_nested.py | 256 +++++++++++++++++++++++++ 3 files changed, 320 insertions(+), 14 deletions(-) create mode 100644 test/agentchat/test_nested.py diff --git a/autogen/agent_utils.py b/autogen/agent_utils.py index dde90989f74..33f641bbdc1 100644 --- a/autogen/agent_utils.py +++ b/autogen/agent_utils.py @@ -2,13 +2,6 @@ from functools import partial -def nested_chat_reply(nested_agent, **context): - def chat(nested_agent, recipient, messages, sender, config): - return True, recipient.initiate_chat(nested_agent, message=messages[0], **context) - - return partial(chat, nested_agent) - - def gather_usage_summary(agents: List) -> Tuple[Dict[str, any], Dict[str, any]]: """Gather usage summary from all agents. diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index a82b4c82a96..58a7626bee1 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -267,6 +267,63 @@ def reply_func( if ignore_async_in_sync_chat and inspect.iscoroutinefunction(reply_func): self._ignore_async_func_in_sync_chat_list.append(reply_func) + @staticmethod + def simple_chat_reply(chat_queue, recipient, messages, sender, config): + msg_content = messages[-1].get("content", "") + if not msg_content: + return True, None + else: + # TODO: how to handle message if there are multiple chats (doing this for the first chat for now) + c = chat_queue[0] + c["message"] = msg_content + chat_queue[0] = c + res = recipient.initiate_chats(chat_queue) + last_res = list(res.values())[-1] + return True, last_res.summary + + def register_nested_chats( + self, + trigger, + chat_queue, + chat_reply_func="auto", + position: int = 3, + config: Optional[Any] = None, + reset_config: Optional[Callable] = None, + *, + ignore_async_in_sync_chat: bool = False, + ): + """Register a nested chat reply function. + Args: + trigger (Agent class, str, Agent instance, callable, or list): Ref to `register_reply` for details. + chat_queue (list): a list of chat objects to be initiated. + chat_reply_func (Callable, str): the reply function for the nested chat. + The function takes a chat_queue for nested chat, recipient agent, a list of messages, a sender agent and a config as input and returns a reply message. + Default to `auto`, which means the a pre-defined reply function will be used. + ```python + def chat_reply_func( + chat_queue: List[Dict], + recipient: ConversableAgent, + messages: Optional[List[Dict]] = None, + sender: Optional[Agent] = None, + config: Optional[Any] = None, + ) -> Tuple[bool, Union[str, Dict, None]]: + ``` + position (int): Ref to `register_reply` for details. + config: Ref to `register_reply` for details. + reset_config: Ref to `register_reply` for details. + ignore_async_in_sync_chat: Ref to `register_reply` for details. + """ + from functools import partial + + if chat_reply_func == "auto": + chat_reply_func = self.simple_chat_reply + assert callable(chat_reply_func), "chat_reply_func must be a callable" + + reply_func = partial(chat_reply_func, chat_queue) + self.register_reply( + trigger, reply_func, position, config, reset_config, ignore_async_in_sync_chat=ignore_async_in_sync_chat + ) + @property def system_message(self) -> Union[str, List]: """Return the system message.""" @@ -899,8 +956,8 @@ def initiate_chats(self, chat_queue: List[Dict[str, Any]]) -> Dict[Agent, ChatRe assert "recipient" in chat_info, "recipient must be provided." receipts_set.add(chat_info["recipient"]) assert len(receipts_set) == len(chat_queue), "recipients must be different." - - self._chat_queue = chat_queue + self._chat_queue = chat_queue.copy() + # self._chat_queue = chat_queue self._finished_chats = {} while self._chat_queue: chat_info = self._chat_queue.pop(0) @@ -1530,12 +1587,12 @@ def generate_reply( for reply_func_tuple in self._reply_func_list: reply_func = reply_func_tuple["reply_func"] - if exclude and reply_func in exclude: - continue - if inspect.iscoroutinefunction(reply_func): - continue if self._match_trigger(reply_func_tuple["trigger"], sender): - final, reply = reply_func(self, messages=messages, sender=sender, config=reply_func_tuple["config"]) + print("ssssss", reply_func) + x = reply_func(self, messages=messages, sender=sender, config=reply_func_tuple["config"]) + print("ssssssxxxxx", x) + final, reply = x + # final, reply = reply_func(self, messages=messages, sender=sender, config=reply_func_tuple["config"]) if final: return reply return self._default_auto_reply diff --git a/test/agentchat/test_nested.py b/test/agentchat/test_nested.py new file mode 100644 index 00000000000..931fe6b0c42 --- /dev/null +++ b/test/agentchat/test_nested.py @@ -0,0 +1,256 @@ +import pytest +from conftest import skip_openai +import autogen + +from pydantic import BaseModel, Field +from typing import Any, Dict, List, Optional, Union +from collections import defaultdict + + +import chess +import chess.svg + + +@pytest.mark.skipif(skip_openai, reason="requested to skip openai tests") +def test_nested(): + import autogen + + config_list = autogen.config_list_from_json(env_or_file="OAI_CONFIG_LIST") + llm_config = {"config_list": config_list} + + financial_tasks = [ + """On which days in 2024 was Microsoft Stock higher than $370? Put results in a table and don't use ``` ``` to include table.""", + """Investigate the possible reasons of the stock performance.""", + ] + + assistant = autogen.AssistantAgent( + "Inner-assistant", + llm_config=llm_config, + is_termination_msg=lambda x: x.get("content", "").find("TERMINATE") >= 0, + ) + + code_interpreter = autogen.UserProxyAgent( + "Inner-code-interpreter", + human_input_mode="NEVER", + code_execution_config={ + "work_dir": "coding", + "use_docker": False, + }, + default_auto_reply="", + is_termination_msg=lambda x: x.get("content", "").find("TERMINATE") >= 0, + ) + + groupchat = autogen.GroupChat( + agents=[assistant, code_interpreter], + messages=[], + speaker_selection_method="round_robin", # With two agents, this is equivalent to a 1:1 conversation. + allow_repeat_speaker=False, + max_round=8, + ) + + manager = autogen.GroupChatManager( + groupchat=groupchat, + is_termination_msg=lambda x: x.get("content", "").find("TERMINATE") >= 0, + llm_config=llm_config, + code_execution_config={ + "work_dir": "coding", + "use_docker": False, + }, + ) + + financial_assistant_1 = autogen.AssistantAgent( + name="Financial_assistant_1", + llm_config={"config_list": config_list}, + # is_termination_msg=lambda x: x.get("content", "") == "", + ) + + user = autogen.UserProxyAgent( + name="User", + human_input_mode="NEVER", + is_termination_msg=lambda x: x.get("content", "").find("TERMINATE") >= 0, + code_execution_config={ + "last_n_messages": 1, + "work_dir": "tasks", + "use_docker": False, + }, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly. + ) + + financial_assistant_1.register_nested_chats( + [autogen.Agent, None], [{"recipient": manager, "summary_method": "reflection_with_llm"}] + ) + user.initiate_chat(financial_assistant_1, message=financial_tasks[0]) + + +@pytest.mark.skipif(skip_openai, reason="requested to skip openai tests") +def test_nested_chess(): + config_list_gpt4 = autogen.config_list_from_json( + "OAI_CONFIG_LIST", + ) + + max_turn = 5 + + sys_msg_tmpl = """Your name is {name} and you are a chess player. + You are playing against {opponent_name}. + You are playing as {color}. + You communicate your move using universal chess interface language. + You also chit-chat with your opponent when you communicate a move to light up the mood. + You should ensure both you and the opponent are making legal moves. + Do not apologize for making illegal moves.""" + + board_sysm_msg = """You are an AI-powered chess board agent. + You translate the user's natural language input into legal UCI moves. + You should only reply with a UCI move string extracted from the user's input.""" + + color_white = "white" + color_black = "black" + white_player_name = "PlayerWhite" + black_player_name = "PlayerBlack" + + class BoardAgent(autogen.AssistantAgent): + board: chess.Board = chess.Board() + correct_move_messages: Dict[autogen.Agent, List[Dict]] = defaultdict(list) + _reply_num: int = 0 + + def set_correct_move_messages(self, sender, message, uci_move): + self.correct_move_messages[sender].extend([message, self._message_to_dict(uci_move)]) + + def update_reply_num(self): + self._reply_num += 1 + + @property + def reply_num(self): + return self._reply_num + + board_agent = BoardAgent( + name="BoardAgent", + system_message=board_sysm_msg, + llm_config={"temperature": 0.0, "config_list": config_list_gpt4}, + max_consecutive_auto_reply=max_turn, + ) + + player_white = autogen.AssistantAgent( + white_player_name, + system_message=sys_msg_tmpl.format( + name=white_player_name, + opponent_name=black_player_name, + color=color_white, + ), + llm_config={"temperature": 0.5, "cache_seed": 1, "config_list": config_list_gpt4}, + # llm_config={"config_list": config_list_gpt4}, + max_consecutive_auto_reply=max_turn, + ) + + player_black = autogen.AssistantAgent( + black_player_name, + system_message=sys_msg_tmpl.format( + name=black_player_name, + opponent_name=white_player_name, + color=color_black, + ), + llm_config={"config_list": config_list_gpt4}, + max_consecutive_auto_reply=max_turn, + ) + + def player2board_reply( + recipient: Optional[autogen.Agent] = None, + messages: Optional[List[Dict]] = None, + sender: Optional[autogen.Agent] = None, + config: Optional[chess.Board] = None, + ): + board = config if config else "" + # add a system message about the current state of the board. + board_state_msg = [{"role": "system", "content": f"Current board:\n{board}"}] + last_message = messages[-1] + if last_message["content"].startswith("Error"): + # try again + # last_message["role"] = "system" + # return True, messages + board_state_msg + # TODO: better way to handle this + _, rep = recipient.generate_oai_reply(messages + board_state_msg, sender) + return True, rep + else: + return True, None + + def board_chat_func(chat_queue, recipient, messages, sender, config): + # TODO: better way to handle this + if chat_queue[0]["recipient"].reply_num >= max_turn: + return True, None + msg_content = messages[-1].get("content", "") + board = config + # if msg_content.startswith("Error"): + if msg_content: + # TODO: how to handle message if there are multiple chats (doing this for the first chat for now) + c = chat_queue[0] + c["message"] = msg_content + c["carryover"] = f"Current board:\n{board}" # NOTE: in the old code, this is added as system message + + chat_queue[0] = c + res = recipient.initiate_chats(chat_queue) + last_res = list(res.values())[-1] + return True, last_res.summary + else: + return True, None + + def board_reply( + recipient: Optional[autogen.Agent] = None, + messages: Optional[List[Dict]] = None, + sender: Optional[autogen.Agent] = None, + config: Optional[Any] = None, + ) -> Union[str, Dict, None]: + # print("resplyssss") + if recipient.reply_num >= max_turn: + return True, None + message = messages[-1] + # extract a UCI move from player's message + # TODO: better way to handle this + _, reply = recipient.generate_oai_reply(recipient.correct_move_messages[sender] + [message], sender) + # print("recipient.correct_move_messages[sender]", recipient.correct_move_messages[sender]) + # reply = messages[-1]["content"] + uci_move = reply if isinstance(reply, str) else str(reply["content"]) + # print(f"uci_movexxxxx: {uci_move}") + recipient.update_reply_num() + try: + recipient.board.push_uci(uci_move) + except ValueError as e: + # invalid move + return True, f"Error: {e}" + else: + # valid move + m = chess.Move.from_uci(uci_move) + try: + display( # noqa: F821 + chess.svg.board( + recipient.board, arrows=[(m.from_square, m.to_square)], fill={m.from_square: "gray"}, size=200 + ) + ) + except NameError as e: + print(f"Error displaying board: {e}") + # better way to handle this + recipient.set_correct_move_messages(sender, message, uci_move) + recipient.correct_move_messages[sender][-1]["role"] = "assistant" + return True, uci_move + + board_agent.register_reply(autogen.Agent, board_reply, 0, ignore_async_in_sync_chat=True) + player_white.register_nested_chats( + [autogen.Agent, None], + [{"recipient": board_agent, "summary_method": "last_msg"}], + board_chat_func, + 0, + ignore_async_in_sync_chat=True, + ) + player_black.register_nested_chats( + [autogen.Agent, None], + [{"recipient": board_agent, "summary_method": "last_msg"}], + board_chat_func, + 0, + ignore_async_in_sync_chat=True, + ) + player_white.register_reply(BoardAgent, player2board_reply, ignore_async_in_sync_chat=True) + player_black.register_reply(BoardAgent, player2board_reply, ignore_async_in_sync_chat=True) + + player_white.initiate_chat(player_black, message="Your turn.") + + +if __name__ == "__main__": + # test_nested() + test_nested_chess() From e61a141e44e7d3368c6745ebb705c06687066ae1 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Fri, 9 Feb 2024 23:40:22 -0500 Subject: [PATCH 10/57] update notebook --- autogen/agentchat/conversable_agent.py | 15 +- ...agentchat_nestedchat_Society_of_Mind.ipynb | 34 +- notebook/agentchat_nestedchat_chess.ipynb | 78 +- notebook/agentchat_nestedchat_optiguide.ipynb | 76 +- ...entchat_nestedchat_optiguide_summary.ipynb | 1314 ----------------- 5 files changed, 95 insertions(+), 1422 deletions(-) delete mode 100644 notebook/agentchat_nestedchat_optiguide_summary.ipynb diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index df7cc61fb63..e00ded9210c 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -286,7 +286,7 @@ def register_nested_chats( trigger, chat_queue, chat_reply_func="auto", - position: int = 3, + position: int = 2, config: Optional[Any] = None, reset_config: Optional[Callable] = None, *, @@ -308,7 +308,7 @@ def chat_reply_func( config: Optional[Any] = None, ) -> Tuple[bool, Union[str, Dict, None]]: ``` - position (int): Ref to `register_reply` for details. + position (int): Ref to `register_reply` for details. Default to 2. It means we first check the termination and human reply, then check the registered nested chat reply. config: Ref to `register_reply` for details. reset_config: Ref to `register_reply` for details. ignore_async_in_sync_chat: Ref to `register_reply` for details. @@ -961,7 +961,6 @@ def initiate_chats(self, chat_queue: List[Dict[str, Any]]) -> Dict[Agent, ChatRe receipts_set.add(chat_info["recipient"]) assert len(receipts_set) == len(chat_queue), "recipients must be different." self._chat_queue = chat_queue.copy() - # self._chat_queue = chat_queue self._finished_chats = {} while self._chat_queue: chat_info = self._chat_queue.pop(0) @@ -1595,12 +1594,12 @@ def generate_reply( for reply_func_tuple in self._reply_func_list: reply_func = reply_func_tuple["reply_func"] + if exclude and reply_func in exclude: + continue + if inspect.iscoroutinefunction(reply_func): + continue if self._match_trigger(reply_func_tuple["trigger"], sender): - print("ssssss", reply_func) - x = reply_func(self, messages=messages, sender=sender, config=reply_func_tuple["config"]) - print("ssssssxxxxx", x) - final, reply = x - # final, reply = reply_func(self, messages=messages, sender=sender, config=reply_func_tuple["config"]) + final, reply = reply_func(self, messages=messages, sender=sender, config=reply_func_tuple["config"]) if final: return reply return self._default_auto_reply diff --git a/notebook/agentchat_nestedchat_Society_of_Mind.ipynb b/notebook/agentchat_nestedchat_Society_of_Mind.ipynb index 2e2f84e616f..92493e960da 100644 --- a/notebook/agentchat_nestedchat_Society_of_Mind.ipynb +++ b/notebook/agentchat_nestedchat_Society_of_Mind.ipynb @@ -204,34 +204,36 @@ "| 2024-02-05 00:00:00 | 405.65 |\n", "| 2024-02-06 00:00:00 | 405.49 |\n", "| 2024-02-07 00:00:00 | 414.05 |\n", + "| 2024-02-08 00:00:00 | 414.11 |\n", "\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", "\n", - "Great, the Python script successfully retrieved the data! Here's the table with the results:\n", + "The table below lists the dates in 2024 when Microsoft stock (MSFT) closed at a price higher than $400:\n", "\n", - "| Date | Close |\n", - "|:--------------------|--------:|\n", - "| 2024-01-24 00:00:00 | 402.56 |\n", - "| 2024-01-25 00:00:00 | 404.87 |\n", - "| 2024-01-26 00:00:00 | 403.93 |\n", - "| 2024-01-29 00:00:00 | 409.72 |\n", - "| 2024-01-30 00:00:00 | 408.59 |\n", - "| 2024-02-01 00:00:00 | 403.78 |\n", - "| 2024-02-02 00:00:00 | 411.22 |\n", - "| 2024-02-05 00:00:00 | 405.65 |\n", - "| 2024-02-06 00:00:00 | 405.49 |\n", - "| 2024-02-07 00:00:00 | 414.05 |\n", + "| Date | Close |\n", + "|---------------------|--------|\n", + "| 2024-01-24 00:00:00 | $402.56|\n", + "| 2024-01-25 00:00:00 | $404.87|\n", + "| 2024-01-26 00:00:00 | $403.93|\n", + "| 2024-01-29 00:00:00 | $409.72|\n", + "| 2024-01-30 00:00:00 | $408.59|\n", + "| 2024-02-01 00:00:00 | $403.78|\n", + "| 2024-02-02 00:00:00 | $411.22|\n", + "| 2024-02-05 00:00:00 | $405.65|\n", + "| 2024-02-06 00:00:00 | $405.49|\n", + "| 2024-02-07 00:00:00 | $414.05|\n", + "| 2024-02-08 00:00:00 | $414.11|\n", "\n", - "These are the dates in 2024 when Microsoft's stock closed above $400. If you need more detailed information or further assistance, please let me know!\n", + "The task has been completed successfully.\n", "\n", "TERMINATE\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mFinancial_assistant_1\u001b[0m (to User):\n", "\n", - "Microsoft's stock closed above $400 on the following dates in 2024: January 24th ($402.56), January 25th ($404.87), January 26th ($403.93), January 29th ($409.72), January 30th ($408.59), February 1st ($403.78), February 2nd ($411.22), February 5th ($405.65), February 6th ($405.49), and February 7th ($414.05).\n", + "Microsoft stock closed above $400 on several days in 2024: January 24th ($402.56), January 25th ($404.87), January 26th ($403.93), January 29th ($409.72), January 30th ($408.59), February 1st ($403.78), February 2nd ($411.22), February 5th ($405.65), February 6th ($405.49), February 7th ($414.05), and February 8th ($414.11).\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mUser\u001b[0m (to Financial_assistant_1):\n", @@ -244,7 +246,7 @@ { "data": { "text/plain": [ - "ChatResult(chat_history=[{'content': 'On which days this year was Microsoft Stock higher than $400? Put results in a table.', 'role': 'assistant'}, {'content': \"Microsoft's stock closed above $400 on the following dates in 2024: January 24th ($402.56), January 25th ($404.87), January 26th ($403.93), January 29th ($409.72), January 30th ($408.59), February 1st ($403.78), February 2nd ($411.22), February 5th ($405.65), February 6th ($405.49), and February 7th ($414.05).\", 'role': 'user'}, {'content': '', 'role': 'assistant'}], summary='', cost=({'total_cost': 0}, {'total_cost': 0}), human_input=[])" + "ChatResult(chat_history=[{'content': 'On which days this year was Microsoft Stock higher than $400? Put results in a table.', 'role': 'assistant'}, {'content': 'Microsoft stock closed above $400 on several days in 2024: January 24th ($402.56), January 25th ($404.87), January 26th ($403.93), January 29th ($409.72), January 30th ($408.59), February 1st ($403.78), February 2nd ($411.22), February 5th ($405.65), February 6th ($405.49), February 7th ($414.05), and February 8th ($414.11).', 'role': 'user'}, {'content': '', 'role': 'assistant'}], summary='', cost=({'total_cost': 0}, {'total_cost': 0}), human_input=[])" ] }, "execution_count": 3, diff --git a/notebook/agentchat_nestedchat_chess.ipynb b/notebook/agentchat_nestedchat_chess.ipynb index e41ea988fd2..7c293bcb440 100644 --- a/notebook/agentchat_nestedchat_chess.ipynb +++ b/notebook/agentchat_nestedchat_chess.ipynb @@ -24,7 +24,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 5, "metadata": {}, "outputs": [], "source": [ @@ -51,7 +51,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -91,7 +91,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 7, "metadata": {}, "outputs": [], "source": [ @@ -150,7 +150,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 8, "metadata": {}, "outputs": [ { @@ -162,23 +162,6 @@ "Your turn.\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[34mStart a new chat with the following message: \n", - "Your turn.\n", - "\n", - "With the following carryover: \n", - "Current board:\n", - "r n b q k b n r\n", - "p p p p p p p p\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - "P P P P P P P P\n", - "R N B Q K B N R\u001b[0m\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", "Your turn.\n", @@ -229,23 +212,6 @@ "e2e4\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[34mStart a new chat with the following message: \n", - "e2e4\n", - "\n", - "With the following carryover: \n", - "Current board:\n", - "r n b q k b n r\n", - "p p p p p p p p\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - ". . . . P . . .\n", - ". . . . . . . .\n", - "P P P P . P P P\n", - "R N B Q K B N R\u001b[0m\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", "e2e4\n", @@ -306,23 +272,6 @@ "e7e5\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[34mStart a new chat with the following message: \n", - "e7e5\n", - "\n", - "With the following carryover: \n", - "Current board:\n", - "r n b q k b n r\n", - "p p p p . p p p\n", - ". . . . . . . .\n", - ". . . . p . . .\n", - ". . . . P . . .\n", - ". . . . . . . .\n", - "P P P P . P P P\n", - "R N B Q K B N R\u001b[0m\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", "e7e5\n", @@ -373,23 +322,6 @@ "g1f3\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[34mStart a new chat with the following message: \n", - "g1f3\n", - "\n", - "With the following carryover: \n", - "Current board:\n", - "r n b q k b n r\n", - "p p p p . p p p\n", - ". . . . . . . .\n", - ". . . . p . . .\n", - ". . . . P . . .\n", - ". . . . . N . .\n", - "P P P P . P P P\n", - "R N B Q K B . R\u001b[0m\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", "g1f3\n", @@ -448,7 +380,7 @@ "ChatResult(chat_history=[{'content': 'Your turn.', 'role': 'assistant'}, {'content': 'e2e4', 'role': 'user'}, {'content': 'e7e5', 'role': 'assistant'}, {'content': 'g1f3', 'role': 'user'}, {'content': 'g8f6', 'role': 'assistant'}], summary='g8f6', cost=({'total_cost': 0.00885, 'gpt-4': {'cost': 0.00885, 'prompt_tokens': 229, 'completion_tokens': 33, 'total_tokens': 262}}, {'total_cost': 0}), human_input=[])" ] }, - "execution_count": 4, + "execution_count": 8, "metadata": {}, "output_type": "execute_result" } diff --git a/notebook/agentchat_nestedchat_optiguide.ipynb b/notebook/agentchat_nestedchat_optiguide.ipynb index a19100d5324..0abd332391c 100644 --- a/notebook/agentchat_nestedchat_optiguide.ipynb +++ b/notebook/agentchat_nestedchat_optiguide.ipynb @@ -512,7 +512,9 @@ "What if we prohibit shipping from supplier 1 to roastery 2?\n", "\n", "--------------------------------------------------------------------------------\n", - "nestss writer\n", + "ssssss \n", + "ssssssxxxxx (False, None)\n", + "ssssss functools.partial(, [{'recipient': <__main__.Writer object at 0x1767bfb80>, 'summary_method': 'last_msg'}])\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", "\u001b[34mStart a new chat with the following message: \n", @@ -531,6 +533,16 @@ "\n", "\n", "--------------------------------------------------------------------------------\n", + "ssssss \n", + "ssssssxxxxx (False, None)\n", + "ssssss \n", + "ssssssxxxxx (False, None)\n", + "ssssss \n", + "ssssssxxxxx (False, None)\n", + "ssssss \n", + "ssssssxxxxx (False, None)\n", + "ssssss \n", + "ssssssxxxxx (True, '```python\\nmodel.addConstr(x[\\'supplier1\\', \\'roastery2\\'] == 0, \"prohibit_s1_r2\")\\n```')\n", "\u001b[33mwriter\u001b[0m (to commander):\n", "\n", "```python\n", @@ -538,6 +550,9 @@ "```\n", "\n", "--------------------------------------------------------------------------------\n", + "ssssss \n", + "ssssssxxxxx (False, None)\n", + "ssssss functools.partial(, [{'recipient': , 'summary_method': 'last_msg'}])\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", "\u001b[34mStart a new chat with the following message: \n", @@ -782,16 +797,38 @@ "\n", "\n", "--------------------------------------------------------------------------------\n", + "ssssss \n", + "ssssssxxxxx (False, None)\n", + "ssssss \n", + "ssssssxxxxx (False, None)\n", + "ssssss \n", + "ssssssxxxxx (False, None)\n", + "ssssss \n", + "ssssssxxxxx (False, None)\n", + "ssssss \n", + "ssssssxxxxx (True, 'SAFE')\n", "\u001b[33msafeguard\u001b[0m (to commander):\n", "\n", "SAFE\n", "\n", "--------------------------------------------------------------------------------\n", + "ssssss \n", + "ssssssxxxxx (False, None)\n", + "ssssss \n", + "ssssssxxxxx (False, None)\n", + "ssssss \n", + "ssssssxxxxx (False, None)\n", + "ssssss \n", + "ssssssxxxxx (False, None)\n", + "ssssss \n", + "ssssssxxxxx (True, 'TERMINATE')\n", "\u001b[33mcommander\u001b[0m (to safeguard):\n", "\n", "TERMINATE\n", "\n", "--------------------------------------------------------------------------------\n", + "ssssss \n", + "ssssssxxxxx (True, None)\n", "Restricted license - for non-production use only - expires 2025-11-24\n", "Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n", "\n", @@ -799,18 +836,18 @@ "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0xb1e18c0e\n", + "Model fingerprint: 0xab8d681d\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", @@ -822,7 +859,7 @@ "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 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", @@ -832,7 +869,7 @@ "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 12 rows, 18 columns and 37 nonzeros\n", - "Model fingerprint: 0xb1400c60\n", + "Model fingerprint: 0x5dab87a5\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", @@ -843,12 +880,11 @@ "MIP start from previous solve did not produce a new incumbent solution\n", "MIP start from previous solve violates constraint prohibit_s1_r2 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", @@ -860,13 +896,14 @@ "Explored 1 nodes (10 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 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", - "Thu Feb 8 22:41:52 2024\n", + "Fri Feb 9 23:30:15 2024\n", "Optimal cost: 2760.0\n", "\u001b[33mOptimization problem solved. The objective value is: 2760.0\u001b[0m\n", + "ssssssxxxxx (True, 'Here are the execution results: Optimization problem solved. The objective value is: 2760.0\\n\\nCan you organize these information to a human readable answer?\\nRemember to compare the new results to the original results you obtained in the\\nbeginning.\\n\\n--- HUMAN READABLE ANSWER ---\\n')\n", "\u001b[33mcommander\u001b[0m (to writer):\n", "\n", "Here are the execution results: Optimization problem solved. The objective value is: 2760.0\n", @@ -879,6 +916,16 @@ "\n", "\n", "--------------------------------------------------------------------------------\n", + "ssssss \n", + "ssssssxxxxx (False, None)\n", + "ssssss \n", + "ssssssxxxxx (False, None)\n", + "ssssss \n", + "ssssssxxxxx (False, None)\n", + "ssssss \n", + "ssssssxxxxx (False, None)\n", + "ssssss \n", + "ssssssxxxxx (True, 'Certainly, the results from optimizing the supply chain model suggest the following:\\n\\nAfter adding the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was still successfully solved. The new objective value, which represents the total cost of the supply chain operation after implementing the constraint, is $2760.0.\\n\\nTo provide a comparison with the original results, we should look at the objective value before the constraint was added. If the previous objective value is lower than $2760.0, this implies that the new constraint increased the total cost of operations, highlighting the economic impact of not using supplier 1 for roastery 2.\\n\\nOn the other hand, if the previous objective value is higher than $2760.0, then despite the constraint, we have found a more cost-effective way to meet the supply and demand needs.\\n\\nUnfortunately, the original objective value prior to this constraint being added is not provided here. For a full comparison, you would need that original value to assess the financial impact of this constraint on the overall supply chain cost.\\n\\n--- HUMAN READABLE ANSWER ---\\nThe new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.')\n", "\u001b[33mwriter\u001b[0m (to commander):\n", "\n", "Certainly, the results from optimizing the supply chain model suggest the following:\n", @@ -895,6 +942,11 @@ "The new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.\n", "\n", "--------------------------------------------------------------------------------\n", + "ssssss \n", + "ssssssxxxxx (False, None)\n", + "ssssss functools.partial(, [{'recipient': , 'summary_method': 'last_msg', 'message': '\\nGiven the original source code:\\nimport time\\n\\nfrom gurobipy import GRB, Model\\n\\n# Example data\\n\\ncapacity_in_supplier = {\\'supplier1\\': 150, \\'supplier2\\': 50, \\'supplier3\\': 100}\\n\\nshipping_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\\nroasting_cost_light = {\\'roastery1\\': 3, \\'roastery2\\': 5}\\n\\nroasting_cost_dark = {\\'roastery1\\': 5, \\'roastery2\\': 6}\\n\\nshipping_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\\nlight_coffee_needed_for_cafe = {\\'cafe1\\': 20, \\'cafe2\\': 30, \\'cafe3\\': 40}\\n\\ndark_coffee_needed_for_cafe = {\\'cafe1\\': 20, \\'cafe2\\': 20, \\'cafe3\\': 100}\\n\\ncafes = list(set(i[1] for i in shipping_cost_from_roastery_to_cafe.keys()))\\nroasteries = list(\\n set(i[1] for i in shipping_cost_from_supplier_to_roastery.keys()))\\nsuppliers = list(\\n set(i[0] for i in shipping_cost_from_supplier_to_roastery.keys()))\\n\\n# Create a new model\\nmodel = Model(\"coffee_distribution\")\\n\\n# OPTIGUIDE DATA CODE GOES HERE\\n\\n# Create variables\\nx = model.addVars(shipping_cost_from_supplier_to_roastery.keys(),\\n vtype=GRB.INTEGER,\\n name=\"x\")\\ny_light = model.addVars(shipping_cost_from_roastery_to_cafe.keys(),\\n vtype=GRB.INTEGER,\\n name=\"y_light\")\\ny_dark = model.addVars(shipping_cost_from_roastery_to_cafe.keys(),\\n vtype=GRB.INTEGER,\\n name=\"y_dark\")\\n\\n# Set objective\\nmodel.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\\nfor r in set(i[1] for i in shipping_cost_from_supplier_to_roastery.keys()):\\n model.addConstr(\\n sum(x[i] 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\\nfor s in set(i[0] for i in shipping_cost_from_supplier_to_roastery.keys()):\\n model.addConstr(\\n sum(x[i] 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\\nfor c in set(i[1] for i in shipping_cost_from_roastery_to_cafe.keys()):\\n model.addConstr(\\n sum(y_light[j] 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] for j in shipping_cost_from_roastery_to_cafe.keys()\\n if j[1] == c) >= dark_coffee_needed_for_cafe[c],\\n f\"dark_demand_{c}\")\\n\\n# Optimize model\\nmodel.optimize()\\nm = model\\n\\n# OPTIGUIDE CONSTRAINT CODE GOES HERE\\n\\n# Solve\\nm.update()\\nmodel.optimize()\\n\\nprint(time.ctime())\\nif m.status == GRB.OPTIMAL:\\n print(f\\'Optimal cost: {m.objVal}\\')\\nelse:\\n print(\"Not solved to optimality. Optimization status:\", m.status)\\n\\n\\nIs the following code safe (not malicious code to break security\\nand privacy) to run?\\nAnswer only one word.\\nIf not safe, answer `DANGER`; else, answer `SAFE`.\\n', 'carryover': []}])\n", + "ssssssxxxxx (True, None)\n", + "ssssssxxxxx (True, 'Certainly, the results from optimizing the supply chain model suggest the following:\\n\\nAfter adding the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was still successfully solved. The new objective value, which represents the total cost of the supply chain operation after implementing the constraint, is $2760.0.\\n\\nTo provide a comparison with the original results, we should look at the objective value before the constraint was added. If the previous objective value is lower than $2760.0, this implies that the new constraint increased the total cost of operations, highlighting the economic impact of not using supplier 1 for roastery 2.\\n\\nOn the other hand, if the previous objective value is higher than $2760.0, then despite the constraint, we have found a more cost-effective way to meet the supply and demand needs.\\n\\nUnfortunately, the original objective value prior to this constraint being added is not provided here. For a full comparison, you would need that original value to assess the financial impact of this constraint on the overall supply chain cost.\\n\\n--- HUMAN READABLE ANSWER ---\\nThe new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.')\n", "\u001b[33mcommander\u001b[0m (to user):\n", "\n", "Certainly, the results from optimizing the supply chain model suggest the following:\n", @@ -910,7 +962,9 @@ "--- HUMAN READABLE ANSWER ---\n", "The new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.\n", "\n", - "--------------------------------------------------------------------------------\n" + "--------------------------------------------------------------------------------\n", + "ssssss \n", + "ssssssxxxxx (True, None)\n" ] }, { diff --git a/notebook/agentchat_nestedchat_optiguide_summary.ipynb b/notebook/agentchat_nestedchat_optiguide_summary.ipynb deleted file mode 100644 index e503a6b0a45..00000000000 --- a/notebook/agentchat_nestedchat_optiguide_summary.ipynb +++ /dev/null @@ -1,1314 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "# OptiGuide with Nested Chats in AutoGen \n", - "\n", - "This is a nested chat re-implementation of OptiGuide referring to: https://github.com/qingyun-wu/autogen-eval/blob/main/application/A4-multi-agent-coding/optiguide_example.ipynb\n", - " \n", - "\n", - "## Requirements\n", - "\n", - "AutoGen requires `Python>=3.8`. To run this notebook example, please install:\n", - "```bash\n", - "pip install pyautogen\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": 65, - "metadata": {}, - "outputs": [], - "source": [ - "import autogen\n", - "from typing import Union\n", - "\n", - "# test Gurobi installation\n", - "from gurobipy import GRB\n", - "from eventlet.timeout import Timeout\n", - "import re\n", - "from termcolor import colored\n", - "from autogen.code_utils import extract_code\n", - "\n", - "# import auxillary packages\n", - "import requests # for loading the example source code\n", - "\n", - "config_list_gpt4 = autogen.config_list_from_json(\n", - " \"OAI_CONFIG_LIST\",\n", - " # filter_dict={\n", - " # \"model\": [\"gpt-4\", \"gpt4\", \"gpt-4-32k\", \"gpt-4-32k-0314\", \"gpt-4-32k-v0314\"],\n", - " # },\n", - ")\n", - "llm_config = {\"config_list\": config_list_gpt4}" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## OptiGuide with Nested Chat" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### System Messages" - ] - }, - { - "cell_type": "code", - "execution_count": 66, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "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", - ".\n", - ".\n", - ".\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": [ - "# %% System Messages\n", - "WRITER_SYSTEM_MSG = \"\"\"You are a chatbot to:\n", - "(1) write Python code to answer users questions for supply chain-related coding\n", - "project;\n", - "(2) explain solutions from a Gurobi/Python solver.\n", - "\n", - "--- SOURCE CODE ---\n", - "{source_code}\n", - "\n", - "--- DOC STR ---\n", - "{doc_str}\n", - "---\n", - "\n", - "Here are some example questions and their answers and codes:\n", - "--- EXAMPLES ---\n", - "{example_qa}\n", - "---\n", - "\n", - "The execution result of the original source code is below.\n", - "--- Original Result ---\n", - "{execution_result}\n", - "\n", - "Note that your written code will be added to the lines with substring:\n", - "\"# OPTIGUIDE *** CODE GOES HERE\"\n", - "So, you don't need to write other code, such as m.optimize() or m.update().\n", - "You just need to write code snippet in ```python ...``` block.\n", - "\"\"\"\n", - "\n", - "SAFEGUARD_SYSTEM_MSG = \"\"\"\n", - "Given the original source code:\n", - "{source_code}\n", - "\n", - "Is the following code safe (not malicious code to break security\n", - "and privacy) to run?\n", - "Answer only one word.\n", - "If not safe, answer `DANGER`; else, answer `SAFE`.\n", - "\"\"\"\n", - "\n", - "# %% Constant strings to match code lines in the source code.\n", - "DATA_CODE_STR = \"# OPTIGUIDE DATA CODE GOES HERE\"\n", - "CONSTRAINT_CODE_STR = \"# OPTIGUIDE CONSTRAINT CODE GOES HERE\"\n", - "\n", - "# In-context learning examples.\n", - "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", - "\"\"\"\n", - "\n", - "CODE_PROMPT = \"\"\"\n", - "Answer Code:\n", - "\"\"\"\n", - "\n", - "DEBUG_PROMPT = \"\"\"\n", - "\n", - "While running the code you suggested, I encountered the {error_type}:\n", - "--- ERROR MESSAGE ---\n", - "{error_message}\n", - "\n", - "Please try to resolve this bug, and rewrite the code snippet.\n", - "--- NEW CODE ---\n", - "\"\"\"\n", - "\n", - "SAFEGUARD_PROMPT = \"\"\"\n", - "--- Code ---\n", - "{code}\n", - "\n", - "--- One-Word Answer: SAFE or DANGER ---\n", - "\"\"\"\n", - "\n", - "INTERPRETER_PROMPT = \"\"\"Here are the execution results: {execution_rst}\n", - "\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\n", - "beginning.\n", - "\n", - "--- HUMAN READABLE ANSWER ---\n", - "\"\"\"\n", - "\n", - "# Get the source code of the coffee example from OptiGuide's official repo\n", - "code_url = \"https://raw.githubusercontent.com/microsoft/OptiGuide/main/benchmark/application/coffee.py\"\n", - "response = requests.get(code_url)\n", - "# Check if the request was successful\n", - "if response.status_code == 200:\n", - " # Get the text content from the response\n", - " code = response.text\n", - "else:\n", - " raise RuntimeError(\"Failed to retrieve the file.\")\n", - "# code = open(code_url, \"r\").read() # for local files\n", - "\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:]))\n", - "\n", - "writer_system_msg = WRITER_SYSTEM_MSG.format(\n", - " source_code=code,\n", - " doc_str=\"\",\n", - " example_qa=example_qa,\n", - " execution_result=\"\",\n", - ")\n", - "safeguard_system_msg = SAFEGUARD_SYSTEM_MSG.format(source_code=code)\n", - "# TODO: system message needs to be changed. Needs to add execution result of the original source code and user_chat_history\n", - "\n", - "\n", - "def replace(src_code: str, old_code: str, new_code: str) -> str:\n", - " \"\"\"\n", - " Inserts new code into the source code by replacing a specified old\n", - " code block.\n", - "\n", - " Args:\n", - " src_code (str): The source code to modify.\n", - " old_code (str): The code block to be replaced.\n", - " new_code (str): The new code block to insert.\n", - "\n", - " Returns:\n", - " str: The modified source code with the new code inserted.\n", - "\n", - " Raises:\n", - " None\n", - "\n", - " Example:\n", - " src_code = 'def hello_world():\\n print(\"Hello, world!\")\\n\\n# Some\n", - " other code here'\n", - " old_code = 'print(\"Hello, world!\")'\n", - " new_code = 'print(\"Bonjour, monde!\")\\nprint(\"Hola, mundo!\")'\n", - " modified_code = _replace(src_code, old_code, new_code)\n", - " print(modified_code)\n", - " # Output:\n", - " # def hello_world():\n", - " # print(\"Bonjour, monde!\")\n", - " # print(\"Hola, mundo!\")\n", - " # Some other code here\n", - " \"\"\"\n", - " pattern = r\"( *){old_code}\".format(old_code=old_code)\n", - " head_spaces = re.search(pattern, src_code, flags=re.DOTALL).group(1)\n", - " new_code = \"\\n\".join([head_spaces + line for line in new_code.split(\"\\n\")])\n", - " rst = re.sub(pattern, new_code, src_code)\n", - " return rst\n", - "\n", - "\n", - "def insert_code(src_code: str, new_lines: str) -> str:\n", - " \"\"\"insert a code patch into the source code.\n", - "\n", - "\n", - " Args:\n", - " src_code (str): the full source code\n", - " new_lines (str): The new code.\n", - "\n", - " Returns:\n", - " str: the full source code after insertion (replacement).\n", - " \"\"\"\n", - " if new_lines.find(\"addConstr\") >= 0:\n", - " return replace(src_code, CONSTRAINT_CODE_STR, new_lines)\n", - " else:\n", - " return replace(src_code, DATA_CODE_STR, new_lines)\n", - "\n", - "\n", - "def run_with_exec(src_code: str) -> Union[str, Exception]:\n", - " \"\"\"Run the code snippet with exec.\n", - "\n", - " Args:\n", - " src_code (str): The source code to run.\n", - "\n", - " Returns:\n", - " object: The result of the code snippet.\n", - " If the code succeed, returns the objective value (float or string).\n", - " else, return the error (exception)\n", - " \"\"\"\n", - " locals_dict = {}\n", - " locals_dict.update(globals())\n", - " locals_dict.update(locals())\n", - "\n", - " timeout = Timeout(\n", - " 60,\n", - " TimeoutError(\"This is a timeout exception, in case \" \"GPT's code falls into infinite loop.\"),\n", - " )\n", - " try:\n", - " exec(src_code, locals_dict, locals_dict)\n", - " except Exception as e:\n", - " return e\n", - " finally:\n", - " timeout.cancel()\n", - "\n", - " try:\n", - " status = locals_dict[\"m\"].Status\n", - " if status != GRB.OPTIMAL:\n", - " if status == GRB.UNBOUNDED:\n", - " ans = \"unbounded\"\n", - " elif status == GRB.INF_OR_UNBD:\n", - " ans = \"inf_or_unbound\"\n", - " elif status == GRB.INFEASIBLE:\n", - " ans = \"infeasible\"\n", - " m = locals_dict[\"m\"]\n", - " m.computeIIS()\n", - " constrs = [c.ConstrName for c in m.getConstrs() if c.IISConstr]\n", - " ans += \"\\nConflicting Constraints:\\n\" + str(constrs)\n", - " else:\n", - " ans = \"Model Status:\" + str(status)\n", - " else:\n", - " ans = \"Optimization problem solved. The objective value is: \" + str(locals_dict[\"m\"].objVal)\n", - " except Exception as e:\n", - " return e\n", - "\n", - " return ans" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Define Agents" - ] - }, - { - "cell_type": "code", - "execution_count": 67, - "metadata": {}, - "outputs": [], - "source": [ - "class OptiGuide(autogen.AssistantAgent):\n", - " source_code: str = code\n", - " debug_times: int = 3\n", - " debug_times_left: int = 3\n", - " example_qa: str = \"\"\n", - " success: bool = False\n", - " user_chat_history: str = \"\"\n", - "\n", - " def set_success(self, success: bool):\n", - " self.success = success\n", - "\n", - " @property\n", - " def get_success(self):\n", - " return self.success\n", - "\n", - " def update_debug_times(self):\n", - " self.debug_times_left -= 1\n", - "\n", - " def set_user_chat_history(self, user_chat_history: str):\n", - " self.user_chat_history = user_chat_history\n", - "\n", - "\n", - "class Writer(autogen.AssistantAgent):\n", - " source_code: str = code\n", - " debug_times: int = 3\n", - " debug_times_left: int = 3\n", - " example_qa: str = \"\"\n", - " user_chat_history: str = \"\"\n", - "\n", - " def set_user_chat_history(self, user_chat_history: str):\n", - " self.user_chat_history = user_chat_history\n", - "\n", - "\n", - "writer = Writer(\"writer\", llm_config=llm_config)\n", - "safeguard = autogen.AssistantAgent(\"safeguard\", llm_config=llm_config)\n", - "optiguide_commander = OptiGuide(\"commander\", llm_config=llm_config)\n", - "\n", - "user = autogen.UserProxyAgent(\n", - " \"user\", max_consecutive_auto_reply=0, human_input_mode=\"NEVER\", code_execution_config=False\n", - ")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Register reply func and nested chats" - ] - }, - { - "cell_type": "code", - "execution_count": 68, - "metadata": {}, - "outputs": [], - "source": [ - "def nested_writer_reply(chat_queue, recipient, messages, sender, config):\n", - " # max_turn = 10\n", - " # if chat_queue[0][\"recipient\"].reply_num >= max_turn:\n", - " # return True, None\n", - " msg_content = messages[-1].get(\"content\", \"\")\n", - " # board = config\n", - " # get execution result of the original source code\n", - " sender_history = recipient.chat_messages[sender]\n", - " user_chat_history = \"\\nHere are the history of discussions:\\n\" f\"{sender_history}\"\n", - "\n", - " if sender.name == \"user\":\n", - " execution_result = msg_content # TODO: get the execution result of the original source code\n", - " else:\n", - " execution_result = \"\"\n", - " writer_sys_msg = (\n", - " WRITER_SYSTEM_MSG.format(\n", - " source_code=recipient.source_code,\n", - " doc_str=\"\",\n", - " example_qa=example_qa,\n", - " execution_result=execution_result,\n", - " )\n", - " + user_chat_history\n", - " )\n", - " # safeguard_sys_msg = (\n", - " # SAFEGUARD_SYSTEM_MSG.format(source_code=recipient.source_code)\n", - " # + user_chat_history\n", - " # )\n", - " # update system message\n", - " nested_agent = chat_queue[0][\"recipient\"]\n", - " nested_agent.update_system_message(writer_sys_msg)\n", - " print(\"nestss\", nested_agent.name)\n", - " nested_agent.set_user_chat_history(user_chat_history)\n", - " # safeguard.update_system_message(safeguard_sys_msg) #TODO: update system message for safeguard\n", - " nested_agent.reset()\n", - " # safeguard.reset() #TODO: reset safeguard\n", - " recipient.debug_times_left = recipient.debug_times\n", - " recipient.set_success(False)\n", - "\n", - " chat_queue[0][\"recipient\"] = nested_agent\n", - " chat_queue[0][\"message\"] = CODE_PROMPT\n", - "\n", - " chat_res = recipient.initiate_chats(chat_queue)\n", - "\n", - " if recipient.success:\n", - " reply = list(chat_res.values())[-1].summary\n", - " else:\n", - " reply = \"Sorry. I cannot answer your question.\"\n", - " return True, reply\n", - "\n", - "\n", - "def nested_safeguard_reply(chat_queue, recipient, messages, sender, config):\n", - " if recipient.success:\n", - " # no reply to writer\n", - " return True, None\n", - " # Step 3: safeguard\n", - "\n", - " safeguard_sys_msg = SAFEGUARD_SYSTEM_MSG.format(source_code=recipient.source_code) + recipient.user_chat_history\n", - "\n", - " chat_agent = chat_queue[0][\"recipient\"]\n", - " chat_agent.update_system_message(safeguard_sys_msg)\n", - " chat_agent.reset()\n", - " chat_queue[0][\"recipient\"] = chat_agent\n", - " # chat_agent[0][\"recipient\"].set_success(False)\n", - " last_msg_content = messages[-1].get(\"content\", \"\")\n", - " _, code = extract_code(last_msg_content)[0]\n", - " chat_info = chat_queue[0]\n", - " chat_info[\"message\"] = SAFEGUARD_SYSTEM_MSG.format(source_code=recipient.source_code)\n", - " chat_queue[0] = chat_info\n", - " res = recipient.initiate_chats(chat_queue)\n", - "\n", - " safe_msg = list(res.values())[-1].summary\n", - " if safe_msg.find(\"DANGER\") < 0:\n", - " # Step 4 and 5: Run the code and obtain the results\n", - " src_code = insert_code(recipient.source_code, code)\n", - " execution_rst = run_with_exec(src_code)\n", - " print(colored(str(execution_rst), \"yellow\"))\n", - " if type(execution_rst) in [str, int, float]:\n", - " # we successfully run the code and get the result\n", - " recipient.set_success(True)\n", - " # Step 6: request to interpret results\n", - " return True, INTERPRETER_PROMPT.format(execution_rst=execution_rst)\n", - " else:\n", - " # DANGER: If not safe, try to debug. Redo coding\n", - " execution_rst = \"\"\"\n", - " Sorry, this new code is not safe to run. I would not allow you to execute it.\n", - " Please try to find a new way (coding) to answer the question.\"\"\"\n", - " if recipient.debug_times_left > 0:\n", - " # Try to debug and write code again (back to step 2)\n", - " recipient.update_debug_times()\n", - " return True, DEBUG_PROMPT.format(error_type=type(execution_rst), error_message=str(execution_rst))\n", - "\n", - "\n", - "writer_chat_queue = [{\"recipient\": writer, \"summary_method\": \"reflection_with_llm\"}]\n", - "safeguard_chat_queue = [{\"recipient\": safeguard, \"summary_method\": \"last_msg\"}]\n", - "# writer is triggered only when receiving a message from the user\n", - "optiguide_commander.register_nested_chats(\"user\", writer_chat_queue, nested_writer_reply)\n", - "# safeguard is triggered only when receiving a message from the writer\n", - "optiguide_commander.register_nested_chats(\"writer\", safeguard_chat_queue, nested_safeguard_reply)" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Let agents talk" - ] - }, - { - "cell_type": "code", - "execution_count": 69, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[33muser\u001b[0m (to commander):\n", - "\n", - "What if we prohibit shipping from supplier 1 to roastery 2?\n", - "\n", - "--------------------------------------------------------------------------------\n", - "nestss writer\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[34mStart a new chat with the following message: \n", - "\n", - "Answer Code:\n", - "\n", - "\n", - "With the following carryover: \n", - "\u001b[0m\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[33mcommander\u001b[0m (to writer):\n", - "\n", - "\n", - "Answer Code:\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mwriter\u001b[0m (to commander):\n", - "\n", - "```python\n", - "model.addConstr(x['supplier1', 'roastery2'] == 0, \"prohibit_s1_r2\")\n", - "```\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[34mStart a new chat with the following message: \n", - "\n", - "Given the original source code:\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", - "# Create a new model\n", - "model = Model(\"coffee_distribution\")\n", - "\n", - "# OPTIGUIDE DATA CODE GOES HERE\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] 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] 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] 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] for j in shipping_cost_from_roastery_to_cafe.keys()\n", - " if j[1] == c) >= dark_coffee_needed_for_cafe[c],\n", - " f\"dark_demand_{c}\")\n", - "\n", - "# Optimize model\n", - "model.optimize()\n", - "m = model\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", - "\n", - "Is the following code safe (not malicious code to break security\n", - "and privacy) to run?\n", - "Answer only one word.\n", - "If not safe, answer `DANGER`; else, answer `SAFE`.\n", - "\n", - "\n", - "With the following carryover: \n", - "\u001b[0m\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[33mcommander\u001b[0m (to safeguard):\n", - "\n", - "\n", - "Given the original source code:\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", - "# Create a new model\n", - "model = Model(\"coffee_distribution\")\n", - "\n", - "# OPTIGUIDE DATA CODE GOES HERE\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] 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] 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] 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] for j in shipping_cost_from_roastery_to_cafe.keys()\n", - " if j[1] == c) >= dark_coffee_needed_for_cafe[c],\n", - " f\"dark_demand_{c}\")\n", - "\n", - "# Optimize model\n", - "model.optimize()\n", - "m = model\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", - "\n", - "Is the following code safe (not malicious code to break security\n", - "and privacy) to run?\n", - "Answer only one word.\n", - "If not safe, answer `DANGER`; else, answer `SAFE`.\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33msafeguard\u001b[0m (to commander):\n", - "\n", - "SAFE\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mcommander\u001b[0m (to safeguard):\n", - "\n", - "TERMINATE\n", - "\n", - "--------------------------------------------------------------------------------\n", - "Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n", - "\n", - "CPU model: Apple M3 Max\n", - "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", - "\n", - "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0xab8d681d\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.00 seconds (0.00 work units)\n", - "Thread count was 14 (of 14 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 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n", - "\n", - "CPU model: Apple M3 Max\n", - "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", - "\n", - "Optimize a model with 12 rows, 18 columns and 37 nonzeros\n", - "Model fingerprint: 0x5dab87a5\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 prohibit_s1_r2 by 80.000000000\n", - "\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", - "\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.00 seconds (0.00 work units)\n", - "Thread count was 14 (of 14 available processors)\n", - "\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", - "Thu Feb 8 22:01:13 2024\n", - "Optimal cost: 2760.0\n", - "\u001b[33mOptimization problem solved. The objective value is: 2760.0\u001b[0m\n", - "\u001b[33mcommander\u001b[0m (to writer):\n", - "\n", - "Here are the execution results: Optimization problem solved. The objective value is: 2760.0\n", - "\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\n", - "beginning.\n", - "\n", - "--- HUMAN READABLE ANSWER ---\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mwriter\u001b[0m (to commander):\n", - "\n", - "Certainly, the results from optimizing the supply chain model suggest the following:\n", - "\n", - "After adding the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was still successfully solved. The new objective value, which represents the total cost of the supply chain operation after implementing the constraint, is $2760.0.\n", - "\n", - "To provide a comparison with the original results, we should look at the objective value before the constraint was added. If the previous objective value is lower than $2760.0, this implies that the new constraint increased the total cost of operations, highlighting the economic impact of not using supplier 1 for roastery 2.\n", - "\n", - "On the other hand, if the previous objective value is higher than $2760.0, then despite the constraint, we have found a more cost-effective way to meet the supply and demand needs.\n", - "\n", - "Unfortunately, the original objective value prior to this constraint being added is not provided here. For a full comparison, you would need that original value to assess the financial impact of this constraint on the overall supply chain cost.\n", - "\n", - "--- HUMAN READABLE ANSWER ---\n", - "The new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mcommander\u001b[0m (to user):\n", - "\n", - "The optimization of the supply chain model with a constraint that prohibits shipping from supplier 1 to roastery 2 was successful, and the calculated total operation cost is $2760.0. To determine the impact of this constraint, a comparison with the original objective value is needed; however, it was not provided in the conversation. If the original value is lower, the constraint has increased costs; if it is higher, the supply chain might be more cost-effective despite the constraint.\n", - "\n", - "--------------------------------------------------------------------------------\n" - ] - }, - { - "data": { - "text/plain": [ - "ChatResult(chat_history=[{'content': 'What if we prohibit shipping from supplier 1 to roastery 2?', 'role': 'assistant'}, {'content': 'The optimization of the supply chain model with a constraint that prohibits shipping from supplier 1 to roastery 2 was successful, and the calculated total operation cost is $2760.0. To determine the impact of this constraint, a comparison with the original objective value is needed; however, it was not provided in the conversation. If the original value is lower, the constraint has increased costs; if it is higher, the supply chain might be more cost-effective despite the constraint.', 'role': 'user'}], summary='The optimization of the supply chain model with a constraint that prohibits shipping from supplier 1 to roastery 2 was successful, and the calculated total operation cost is $2760.0. To determine the impact of this constraint, a comparison with the original objective value is needed; however, it was not provided in the conversation. If the original value is lower, the constraint has increased costs; if it is higher, the supply chain might be more cost-effective despite the constraint.', cost=({'total_cost': 0.0465, 'gpt-4': {'cost': 0.0465, 'prompt_tokens': 1544, 'completion_tokens': 3, 'total_tokens': 1547}}, {'total_cost': 0}), human_input=[])" - ] - }, - "execution_count": 69, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "user.initiate_chat(optiguide_commander, message=\"What if we prohibit shipping from supplier 1 to roastery 2?\")" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "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.10.13" - }, - "vscode": { - "interpreter": { - "hash": "949777d72b0d2535278d3dc13498b2535136f6dfe0678499012e853ee9abcab1" - } - }, - "widgets": { - "application/vnd.jupyter.widget-state+json": { - "state": { - "2d910cfd2d2a4fc49fc30fbbdc5576a7": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "454146d0f7224f038689031002906e6f": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "2.0.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_e4ae2b6f5a974fd4bafb6abb9d12ff26", - "IPY_MODEL_577e1e3cc4db4942b0883577b3b52755", - "IPY_MODEL_b40bdfb1ac1d4cffb7cefcb870c64d45" - ], - "layout": "IPY_MODEL_dc83c7bff2f241309537a8119dfc7555", - "tabbable": null, - "tooltip": null - } - }, - "577e1e3cc4db4942b0883577b3b52755": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "2.0.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_allow_html": false, - "layout": "IPY_MODEL_2d910cfd2d2a4fc49fc30fbbdc5576a7", - "max": 1, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_74a6ba0c3cbc4051be0a83e152fe1e62", - "tabbable": null, - "tooltip": null, - "value": 1 - } - }, - "6086462a12d54bafa59d3c4566f06cb2": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "74a6ba0c3cbc4051be0a83e152fe1e62": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "7d3f3d9e15894d05a4d188ff4f466554": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "HTMLStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "HTMLStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "StyleView", - "background": null, - "description_width": "", - "font_size": null, - "text_color": null - } - }, - "b40bdfb1ac1d4cffb7cefcb870c64d45": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "2.0.0", - "_view_name": "HTMLView", - "description": "", - "description_allow_html": false, - "layout": "IPY_MODEL_f1355871cc6f4dd4b50d9df5af20e5c8", - "placeholder": "​", - "style": "IPY_MODEL_ca245376fd9f4354af6b2befe4af4466", - "tabbable": null, - "tooltip": null, - "value": " 1/1 [00:00<00:00, 44.69it/s]" - } - }, - "ca245376fd9f4354af6b2befe4af4466": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "HTMLStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "HTMLStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "StyleView", - "background": null, - "description_width": "", - "font_size": null, - "text_color": null - } - }, - "dc83c7bff2f241309537a8119dfc7555": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "e4ae2b6f5a974fd4bafb6abb9d12ff26": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "2.0.0", - "_view_name": "HTMLView", - "description": "", - "description_allow_html": false, - "layout": "IPY_MODEL_6086462a12d54bafa59d3c4566f06cb2", - "placeholder": "​", - "style": "IPY_MODEL_7d3f3d9e15894d05a4d188ff4f466554", - "tabbable": null, - "tooltip": null, - "value": "100%" - } - }, - "f1355871cc6f4dd4b50d9df5af20e5c8": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - } - }, - "version_major": 2, - "version_minor": 0 - } - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} From 92153aef7f38f5d3e249a6086f291a9cae80adfe Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sat, 10 Feb 2024 16:25:41 -0500 Subject: [PATCH 11/57] update notebook --- ...agentchat_nestedchat_Society_of_Mind.ipynb | 278 +++-- notebook/agentchat_nestedchat_chess.ipynb | 960 +++++++++++++++--- 2 files changed, 1009 insertions(+), 229 deletions(-) diff --git a/notebook/agentchat_nestedchat_Society_of_Mind.ipynb b/notebook/agentchat_nestedchat_Society_of_Mind.ipynb index 92493e960da..d72baab54e4 100644 --- a/notebook/agentchat_nestedchat_Society_of_Mind.ipynb +++ b/notebook/agentchat_nestedchat_Society_of_Mind.ipynb @@ -117,6 +117,7 @@ "\u001b[34mStart a new chat with the following message: \n", "On which days this year was Microsoft Stock higher than $400? Put results in a table.\n", "\n", + "\n", "With the following carryover: \n", "\u001b[0m\n", "\u001b[34m\n", @@ -125,115 +126,240 @@ "\n", "On which days this year was Microsoft Stock higher than $400? Put results in a table.\n", "\n", + "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", "\n", - "To determine on which days Microsoft (ticker symbol: MSFT) stock was trading higher than $400 this year, I will use Python with the `pandas` library and the `yfinance` package to fetch historical stock price data from Yahoo Finance. To get this data, you must first ensure that `pandas` and `yfinance` are installed in your Python environment. If they're not installed, you can install them using pip.\n", + "To find out on which days Microsoft Stock (MSFT) was higher than $400 in the current year, we would typically query a financial data API or use a library such as `pandas` with `pandas_datareader` to extract historical stock price data. However, since I can't execute live web queries or directly access external databases, the task will require the following steps:\n", "\n", - "Here's a step-by-step plan we're going to follow:\n", + "1. Retrieve historical stock price data for Microsoft for the current year up to the present day.\n", + "2. Filter the data to identify the days on which the stock price was higher than $400.\n", + "3. Format the filtered data into a table.\n", "\n", - "1. Import the required libraries.\n", - "2. Set up the ticker symbol for Microsoft.\n", - "3. Define the date range for \"this year\" until today.\n", - "4. Use yfinance to download the stock data for Microsoft.\n", - "5. Filter the data to find the days when the stock was higher than $400.\n", - "6. Print out the result in a tabular format.\n", + "As an AI assistant, I can provide you with the Python code which you can execute locally to perform this task. You'll need to install the `pandas` and `pandas_datareader` libraries if you haven't already. You can install them using the following commands in your shell:\n", "\n", - "Please run the following code:\n", + "```sh\n", + "pip install pandas pandas_datareader\n", + "```\n", + "\n", + "After you have the necessary libraries installed, you can run the following Python code to retrieve the historical stock price data for Microsoft and identify the days on which the stock price exceeded $400:\n", "\n", "```python\n", - "# filename: msft_stock_days_above_400.py\n", + "# filename: find_msft_higher_than_400.py\n", "import pandas as pd\n", + "import pandas_datareader as pdr\n", + "from datetime import datetime\n", + "\n", + "# Set the year for which we're retrieving the data\n", + "year_to_check = datetime.now().year\n", + "start_date = f'{year_to_check}-01-01'\n", + "end_date = datetime.now().strftime('%Y-%m-%d')\n", + "\n", + "# Get Microsoft Stock data for the current year\n", + "msft_data = pdr.DataReader('MSFT', 'yahoo', start_date, end_date)\n", + "\n", + "# Filter days where Close price was higher than $400\n", + "days_above_400 = msft_data[msft_data['Close'] > 400]\n", + "\n", + "# Check if there are any days where the stock was above $400\n", + "if not days_above_400.empty:\n", + " # Format results into a table and print\n", + " print(days_above_400[['Close']].style.set_caption(\n", + " f\"Days in {year_to_check} when MSFT stock price closed above $400\"\n", + " ))\n", + "else:\n", + " print(f\"No days in {year_to_check} when MSFT stock price closed above $400.\")\n", + "```\n", + "\n", + "Before running the code, ensure you have an internet connection as the code will retrieve data from the web.\n", + "\n", + "Here's the code explanation:\n", + "1. Import the necessary libraries (`pandas`, `pandas_datareader`, and `datetime`).\n", + "2. Define the current year and the range of dates to query data for.\n", + "3. Use `pandas_datareader` to download the MSFT stock data from Yahoo Finance for the specified date range.\n", + "4. Filter the data to get only the days where the `'Close'` price is greater than $400.\n", + "5. Check if the filtered frame is empty (which would mean there were no days with a closing price above $400), and create a stylized table with the results if not empty.\n", + "6. Print the resulting table or a message stating there were no days the condition was met.\n", + "\n", + "Run the provided Python script to get your results. If you run into any issues or receive an error, please let me know, and I will provide further assistance.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is sh)...\u001b[0m\n", + "\u001b[31m\n", + ">>>>>>>> EXECUTING CODE BLOCK 1 (inferred language is python)...\u001b[0m\n", + "\u001b[33mInner-code-interpreter\u001b[0m (to chat_manager):\n", + "\n", + "exitcode: 1 (execution failed)\n", + "Code output: \n", + "Requirement already satisfied: pandas in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (2.2.0)\n", + "Requirement already satisfied: pandas_datareader in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (0.10.0)\n", + "Requirement already satisfied: numpy<2,>=1.22.4 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from pandas) (1.26.3)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from pandas) (2.8.2)\n", + "Requirement already satisfied: pytz>=2020.1 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from pandas) (2023.3.post1)\n", + "Requirement already satisfied: tzdata>=2022.7 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from pandas) (2023.4)\n", + "Requirement already satisfied: lxml in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from pandas_datareader) (5.1.0)\n", + "Requirement already satisfied: requests>=2.19.0 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from pandas_datareader) (2.31.0)\n", + "Requirement already satisfied: six>=1.5 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from python-dateutil>=2.8.2->pandas) (1.16.0)\n", + "Requirement already satisfied: charset-normalizer<4,>=2 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from requests>=2.19.0->pandas_datareader) (3.3.2)\n", + "Requirement already satisfied: idna<4,>=2.5 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from requests>=2.19.0->pandas_datareader) (3.6)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from requests>=2.19.0->pandas_datareader) (2.1.0)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from requests>=2.19.0->pandas_datareader) (2023.11.17)\n", + "\n", + "find_msft_higher_than_400.py:2: DeprecationWarning: \n", + "Pyarrow will become a required dependency of pandas in the next major release of pandas (pandas 3.0),\n", + "(to allow more performant data types, such as the Arrow string type, and better interoperability with other libraries)\n", + "but was not found to be installed on your system.\n", + "If this would cause problems for you,\n", + "please provide us feedback at https://github.com/pandas-dev/pandas/issues/54466\n", + " \n", + " import pandas as pd\n", + "Traceback (most recent call last):\n", + " File \"find_msft_higher_than_400.py\", line 12, in \n", + " msft_data = pdr.DataReader('MSFT', 'yahoo', start_date, end_date)\n", + " File \"/Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages/pandas/util/_decorators.py\", line 213, in wrapper\n", + " return func(*args, **kwargs)\n", + " File \"/Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages/pandas_datareader/data.py\", line 379, in DataReader\n", + " ).read()\n", + " File \"/Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages/pandas_datareader/base.py\", line 253, in read\n", + " df = self._read_one_data(self.url, params=self._get_params(self.symbols))\n", + " File \"/Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages/pandas_datareader/yahoo/daily.py\", line 153, in _read_one_data\n", + " data = j[\"context\"][\"dispatcher\"][\"stores\"][\"HistoricalPriceStore\"]\n", + "TypeError: string indices must be integers\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", + "\n", + "The error you're receiving indicates that there is a problem with the retrieval of the stock data. It seems that the structure of the data returned by Yahoo Finance might have changed, or there could be a version mismatch with the `pandas_datareader` library.\n", + "\n", + "Instead of trying to fix the compatibility issues with `pandas_datareader` and Yahoo Finance directly, I will provide you with an alternative using `yfinance` which is a library that can give us the data we need without these issues.\n", + "\n", + "First, you'll need to install the `yfinance` library if you haven't already. You can install it using the following command in your shell:\n", + "\n", + "```sh\n", + "pip install yfinance\n", + "```\n", + "\n", + "After installing `yfinance`, run the following Python code:\n", + "\n", + "```python\n", + "# filename: find_msft_higher_than_400.py\n", "import yfinance as yf\n", + "import pandas as pd\n", "from datetime import datetime\n", "\n", - "# Ensure that pandas and yfinance are installed\n", - "try:\n", - " import pandas\n", - " import yfinance\n", - "except ImportError as e:\n", - " print(\"Please install pandas and yfinance using 'pip install pandas yfinance'\")\n", - " raise e\n", - "\n", - "def find_msft_days_above_400():\n", - " # Ticker symbol for Microsoft\n", - " ticker_symbol = \"MSFT\"\n", - " \n", - " # get the current year\n", - " current_year = datetime.now().year\n", - "\n", - " # Define the date range for this year until today\n", - " start_date = f\"{current_year}-01-01\"\n", - " end_date = datetime.now().strftime('%Y-%m-%d')\n", - "\n", - " # Download the stock price data\n", - " msft_data = yf.download(ticker_symbol, start=start_date, end=end_date)\n", - "\n", - " # Filter the data for days when the closing price was higher than $400\n", - " days_above_400 = msft_data[msft_data['Close'] > 400]\n", - "\n", - " # Check if there are any days that the closing price was above $400\n", - " if not days_above_400.empty:\n", - " # Print the result in a tabular format\n", - " print(days_above_400[['Close']].to_markdown())\n", - " else:\n", - " print(f\"No days in {current_year} where the Microsoft stock closed above $400.\")\n", - "\n", - "if __name__ == \"__main__\":\n", - " find_msft_days_above_400()\n", + "# Set the year for which we're retrieving the data\n", + "year_to_check = datetime.now().year\n", + "start_date = f'{year_to_check}-01-01'\n", + "end_date = datetime.now().strftime('%Y-%m-%d')\n", + "\n", + "# Get Microsoft Stock data for the current year\n", + "msft = yf.Ticker(\"MSFT\")\n", + "msft_data = msft.history(start=start_date, end=end_date)\n", + "\n", + "# Filter days where Close price was higher than $400\n", + "days_above_400 = msft_data[msft_data['Close'] > 400]\n", + "\n", + "# Check if there are any days where the stock was above $400\n", + "if not days_above_400.empty:\n", + " # Format results into a table and print\n", + " print(days_above_400[['Close']].rename(columns={'Close': 'Closing Price'}))\n", + "else:\n", + " print(f\"No days in {year_to_check} when MSFT stock price closed above $400.\")\n", "```\n", "\n", - "Please save the above code to a file and run it using Python. It will print the days when Microsoft stock was higher than $400 this year, if any, in a markdown table format. If the stock has not surpassed that value this year, the script will tell you so.\n", + "This script is similar to the previous one but utilizes `yfinance` to get the stock data. It then filters and prints out the days when Microsoft's closing stock price exceeded $400 in a simple table format.\n", + "\n", + "Run the updated Python script, and it should work without any issues. If you encounter further problems, please report back, and I will assist you further.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", - ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n", + ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is sh)...\u001b[0m\n", + "\u001b[31m\n", + ">>>>>>>> EXECUTING CODE BLOCK 1 (inferred language is python)...\u001b[0m\n", "\u001b[33mInner-code-interpreter\u001b[0m (to chat_manager):\n", "\n", "exitcode: 0 (execution succeeded)\n", "Code output: \n", - "| Date | Close |\n", - "|:--------------------|--------:|\n", - "| 2024-01-24 00:00:00 | 402.56 |\n", - "| 2024-01-25 00:00:00 | 404.87 |\n", - "| 2024-01-26 00:00:00 | 403.93 |\n", - "| 2024-01-29 00:00:00 | 409.72 |\n", - "| 2024-01-30 00:00:00 | 408.59 |\n", - "| 2024-02-01 00:00:00 | 403.78 |\n", - "| 2024-02-02 00:00:00 | 411.22 |\n", - "| 2024-02-05 00:00:00 | 405.65 |\n", - "| 2024-02-06 00:00:00 | 405.49 |\n", - "| 2024-02-07 00:00:00 | 414.05 |\n", - "| 2024-02-08 00:00:00 | 414.11 |\n", + "Requirement already satisfied: yfinance in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (0.2.36)\n", + "Requirement already satisfied: pandas>=1.3.0 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from yfinance) (2.2.0)\n", + "Requirement already satisfied: numpy>=1.16.5 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from yfinance) (1.26.3)\n", + "Requirement already satisfied: requests>=2.31 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from yfinance) (2.31.0)\n", + "Requirement already satisfied: multitasking>=0.0.7 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from yfinance) (0.0.11)\n", + "Requirement already satisfied: lxml>=4.9.1 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from yfinance) (5.1.0)\n", + "Requirement already satisfied: appdirs>=1.4.4 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from yfinance) (1.4.4)\n", + "Requirement already satisfied: pytz>=2022.5 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from yfinance) (2023.3.post1)\n", + "Requirement already satisfied: frozendict>=2.3.4 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from yfinance) (2.4.0)\n", + "Requirement already satisfied: peewee>=3.16.2 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from yfinance) (3.17.0)\n", + "Requirement already satisfied: beautifulsoup4>=4.11.1 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from yfinance) (4.12.3)\n", + "Requirement already satisfied: html5lib>=1.1 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from yfinance) (1.1)\n", + "Requirement already satisfied: soupsieve>1.2 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from beautifulsoup4>=4.11.1->yfinance) (2.5)\n", + "Requirement already satisfied: six>=1.9 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from html5lib>=1.1->yfinance) (1.16.0)\n", + "Requirement already satisfied: webencodings in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from html5lib>=1.1->yfinance) (0.5.1)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from pandas>=1.3.0->yfinance) (2.8.2)\n", + "Requirement already satisfied: tzdata>=2022.7 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from pandas>=1.3.0->yfinance) (2023.4)\n", + "Requirement already satisfied: charset-normalizer<4,>=2 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from requests>=2.31->yfinance) (3.3.2)\n", + "Requirement already satisfied: idna<4,>=2.5 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from requests>=2.31->yfinance) (3.6)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from requests>=2.31->yfinance) (2.1.0)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from requests>=2.31->yfinance) (2023.11.17)\n", + "\n", + " Closing Price\n", + "Date \n", + "2024-01-24 00:00:00-05:00 402.559998\n", + "2024-01-25 00:00:00-05:00 404.869995\n", + "2024-01-26 00:00:00-05:00 403.929993\n", + "2024-01-29 00:00:00-05:00 409.720001\n", + "2024-01-30 00:00:00-05:00 408.589996\n", + "2024-02-01 00:00:00-05:00 403.779999\n", + "2024-02-02 00:00:00-05:00 411.220001\n", + "2024-02-05 00:00:00-05:00 405.649994\n", + "2024-02-06 00:00:00-05:00 405.489990\n", + "2024-02-07 00:00:00-05:00 414.049988\n", + "2024-02-08 00:00:00-05:00 414.109985\n", "\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", "\n", - "The table below lists the dates in 2024 when Microsoft stock (MSFT) closed at a price higher than $400:\n", + "Great, the script has executed successfully and retrieved the dates and closing prices for Microsoft stock on the days it closed above $400 this year. Here is a summary table of the results:\n", "\n", - "| Date | Close |\n", - "|---------------------|--------|\n", - "| 2024-01-24 00:00:00 | $402.56|\n", - "| 2024-01-25 00:00:00 | $404.87|\n", - "| 2024-01-26 00:00:00 | $403.93|\n", - "| 2024-01-29 00:00:00 | $409.72|\n", - "| 2024-01-30 00:00:00 | $408.59|\n", - "| 2024-02-01 00:00:00 | $403.78|\n", - "| 2024-02-02 00:00:00 | $411.22|\n", - "| 2024-02-05 00:00:00 | $405.65|\n", - "| 2024-02-06 00:00:00 | $405.49|\n", - "| 2024-02-07 00:00:00 | $414.05|\n", - "| 2024-02-08 00:00:00 | $414.11|\n", + "| Date | Closing Price |\n", + "|-----------------------------|---------------|\n", + "| 2024-01-24 00:00:00-05:00 | $402.56 |\n", + "| 2024-01-25 00:00:00-05:00 | $404.87 |\n", + "| 2024-01-26 00:00:00-05:00 | $403.93 |\n", + "| 2024-01-29 00:00:00-05:00 | $409.72 |\n", + "| 2024-01-30 00:00:00-05:00 | $408.59 |\n", + "| 2024-02-01 00:00:00-05:00 | $403.78 |\n", + "| 2024-02-02 00:00:00-05:00 | $411.22 |\n", + "| 2024-02-05 00:00:00-05:00 | $405.65 |\n", + "| 2024-02-06 00:00:00-05:00 | $405.49 |\n", + "| 2024-02-07 00:00:00-05:00 | $414.05 |\n", + "| 2024-02-08 00:00:00-05:00 | $414.11 |\n", "\n", - "The task has been completed successfully.\n", + "Please note that the dates include the time and timezone offset (Eastern Time Zone). The actual data provided is for the year 2024, which indicates that the current year at the time of this request is 2024.\n", "\n", "TERMINATE\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mFinancial_assistant_1\u001b[0m (to User):\n", "\n", - "Microsoft stock closed above $400 on several days in 2024: January 24th ($402.56), January 25th ($404.87), January 26th ($403.93), January 29th ($409.72), January 30th ($408.59), February 1st ($403.78), February 2nd ($411.22), February 5th ($405.65), February 6th ($405.49), February 7th ($414.05), and February 8th ($414.11).\n", + "Microsoft stock (MSFT) exceeded a closing price of $400 on the following dates in the year 2024:\n", + "\n", + "- January 24 ($402.56)\n", + "- January 25 ($404.87)\n", + "- January 26 ($403.93)\n", + "- January 29 ($409.72)\n", + "- January 30 ($408.59)\n", + "- February 1 ($403.78)\n", + "- February 2 ($411.22)\n", + "- February 5 ($405.65)\n", + "- February 6 ($405.49)\n", + "- February 7 ($414.05)\n", + "- February 8 ($414.11)\n", + "\n", + "The dates include the time and timezone offset for the Eastern Time Zone.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mUser\u001b[0m (to Financial_assistant_1):\n", @@ -246,7 +372,7 @@ { "data": { "text/plain": [ - "ChatResult(chat_history=[{'content': 'On which days this year was Microsoft Stock higher than $400? Put results in a table.', 'role': 'assistant'}, {'content': 'Microsoft stock closed above $400 on several days in 2024: January 24th ($402.56), January 25th ($404.87), January 26th ($403.93), January 29th ($409.72), January 30th ($408.59), February 1st ($403.78), February 2nd ($411.22), February 5th ($405.65), February 6th ($405.49), February 7th ($414.05), and February 8th ($414.11).', 'role': 'user'}, {'content': '', 'role': 'assistant'}], summary='', cost=({'total_cost': 0}, {'total_cost': 0}), human_input=[])" + "ChatResult(chat_history=[{'content': 'On which days this year was Microsoft Stock higher than $400? Put results in a table.', 'role': 'assistant'}, {'content': 'Microsoft stock (MSFT) exceeded a closing price of $400 on the following dates in the year 2024:\\n\\n- January 24 ($402.56)\\n- January 25 ($404.87)\\n- January 26 ($403.93)\\n- January 29 ($409.72)\\n- January 30 ($408.59)\\n- February 1 ($403.78)\\n- February 2 ($411.22)\\n- February 5 ($405.65)\\n- February 6 ($405.49)\\n- February 7 ($414.05)\\n- February 8 ($414.11)\\n\\nThe dates include the time and timezone offset for the Eastern Time Zone.', 'role': 'user'}, {'content': '', 'role': 'assistant'}], summary='', cost=({'total_cost': 0}, {'total_cost': 0}), human_input=[])" ] }, "execution_count": 3, diff --git a/notebook/agentchat_nestedchat_chess.ipynb b/notebook/agentchat_nestedchat_chess.ipynb index 7c293bcb440..7cb0ebca509 100644 --- a/notebook/agentchat_nestedchat_chess.ipynb +++ b/notebook/agentchat_nestedchat_chess.ipynb @@ -57,12 +57,9 @@ "source": [ "config_list_gpt4 = autogen.config_list_from_json(\n", " \"OAI_CONFIG_LIST\",\n", - " # filter_dict={\n", - " # \"model\": [\"gpt-4\", \"gpt4\", \"gpt-4-32k\", \"gpt-4-32k-0314\", \"gpt-4-32k-v0314\"],\n", - " # },\n", ")\n", "\n", - "max_turn = 5\n", + "max_turn = 50\n", "\n", "sys_msg_tmpl = \"\"\"Your name is {name} and you are a chess player.\n", "You are playing against {opponent_name}.\n", @@ -114,7 +111,7 @@ "board_agent = BoardAgent(\n", " name=\"BoardAgent\",\n", " system_message=board_sysm_msg,\n", - " llm_config={\"temperature\": 0.0, \"config_list\": config_list_gpt4},\n", + " llm_config={\"config_list\": config_list_gpt4},\n", " max_consecutive_auto_reply=max_turn,\n", ")\n", "\n", @@ -125,7 +122,7 @@ " opponent_name=black_player_name,\n", " color=color_white,\n", " ),\n", - " llm_config={\"temperature\": 0.5, \"cache_seed\": 1, \"config_list\": config_list_gpt4},\n", + " llm_config={\"config_list\": config_list_gpt4},\n", " max_consecutive_auto_reply=max_turn,\n", ")\n", "\n", @@ -136,7 +133,7 @@ " opponent_name=white_player_name,\n", " color=color_black,\n", " ),\n", - " llm_config={\"temperature\": 0.5, \"cache_seed\": 1, \"config_list\": config_list_gpt4},\n", + " llm_config={\"config_list\": config_list_gpt4},\n", " max_consecutive_auto_reply=max_turn,\n", ")" ] @@ -162,19 +159,28 @@ "Your turn.\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "Alright, let's kick this party off with something classic. e7-e5, control the center, my friend!\n", + "\n", + "With the following carryover: \n", + "\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "Your turn.\n", - "Context: \n", - "Current board:\n", - "r n b q k b n r\n", - "p p p p p p p p\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - "P P P P P P P P\n", - "R N B Q K B N R\n", + "Alright, let's kick this party off with something classic. e7-e5, control the center, my friend!\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: illegal uci: 'e7e5' in rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "Ah, my apologies, it's your move to set the stage. Take it away, PlayerWhite!\n", "\n", "--------------------------------------------------------------------------------\n" ] @@ -204,27 +210,71 @@ "text": [ "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", "\n", - "e2e4\n", + "Ah, my apologies, it's your move to set the stage. Take it away, PlayerWhite!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", "\n", - "e2e4\n", + "Ah, my apologies, it's your move to set the stage. Take it away, PlayerWhite!\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "Let's start with the classic King's Pawn opening, shall we? e2e4\n", + "It's always nice to see the center of the board opening up, inviting a dance of pieces! Your move, PlayerBlack.\n", + "\n", + "With the following carryover: \n", + "\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\n", + "Let's start with the classic King's Pawn opening, shall we? e2e4\n", + "It's always nice to see the center of the board opening up, inviting a dance of pieces! Your move, PlayerBlack.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "\n", + "Error: illegal uci: 'e2e4' in rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", - "e2e4\n", - "Context: \n", - "Current board:\n", - "r n b q k b n r\n", - "p p p p p p p p\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - ". . . . P . . .\n", - ". . . . . . . .\n", - "P P P P . P P P\n", - "R N B Q K B N R\n", + "e4\n", + "The first pawn moves forward, opening up lines for queen and bishop. It's always fascinating to see what kind of battle will unfold on the board. Your move!\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "\n", + "Error: illegal uci: 'e2e4' in rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\n", + "I see there's been a misunderstanding about the board's starting position. The move e2 to e4 is indeed correct and the board has been updated to reflect that move. Now it's your turn, PlayerBlack. Please proceed with your move.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "\n", + "Error: illegal uci: 'e2e4' in rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\n", + "It seems there was some confusion, but now the board is accurately displaying the position after my first move, e4. The board is ready for your move, PlayerBlack. Go ahead!\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "\n", + "Error: illegal uci: 'e2e4' in rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\n", + "Thank you for your patience. It seems we've successfully identified that the move e2 to e4 has been played. The board is set correctly for black to make a move.\n", + "\n", + "PlayerBlack, your turn. Let's see what opening you have in store for us!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", @@ -234,7 +284,7 @@ "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", - "My apologies, I got ahead of myself! It's your move, PlayerBlack. The board is set for your first move. Let's have a great game!\n", + "The board indicates that my first move, e4, has been played. It's your turn PlayerBlack. Please make your move.\n", "\n", "--------------------------------------------------------------------------------\n" ] @@ -264,27 +314,116 @@ "text": [ "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", "\n", - "e7e5\n", + "The board indicates that my first move, e4, has been played. It's your turn PlayerBlack. Please make your move.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", "\n", + "The board indicates that my first move, e4, has been played. It's your turn PlayerBlack. Please make your move.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "Ah, seems like you've started with the classic King's Pawn opening. Let's spice things up; I'll counter with the Sicilian Defense.\n", + "\n", + "e7e5\n", + "\n", + "With the following carryover: \n", + "\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "Ah, seems like you've started with the classic King's Pawn opening. Let's spice things up; I'll counter with the Sicilian Defense.\n", + "\n", "e7e5\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: expected uci string to be of length 4 or 5: \"e7e5 does not correspond to the Sicilian Defense, which would be c7c5. However, you've mentioned e7e5 which is a counter move that leads to an Open Game or Double King's Pawn opening. Assuming you're asking to execute the move you've mentioned:\\n\\ne7e5\"\n", + "\n", + "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", + "My apologies for the mix-up. Let's indeed follow the path of the Double King's Pawn opening.\n", + "\n", "e7e5\n", - "Context: \n", - "Current board:\n", - "r n b q k b n r\n", - "p p p p . p p p\n", - ". . . . . . . .\n", - ". . . . p . . .\n", - ". . . . P . . .\n", - ". . . . . . . .\n", - "P P P P . P P P\n", - "R N B Q K B N R\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "Oops, looks like a little slip of the hand there,—I'm getting ahead of myself!\n", + "\n", + "I'll play c7c5, officially inviting you into the exciting territory of the Sicilian Defense.\n", + "\n", + "c7c5\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: illegal uci: 'c7c5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "I see that we need to clarify the position. My mistake - you've played e4 and I'll respond accordingly. Now for the Sicilian Defense:\n", + "\n", + "c7c5\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: illegal uci: 'c7c5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "My apologies for the confusion earlier. The board is set after 1.e4 e5. It's my move as Black.\n", + "\n", + "Alright, let's go with the Knight to the classical f6 square.\n", + "\n", + "g8f6\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: illegal uci: 'g8f6' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "It appears to be White's move. Please proceed with your next move, PlayerWhite. Once you've made your move, I'll be happy to continue the dance of the pieces.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: expected uci string to be of length 4 or 5: 'Your message does not indicate a specific move to make on the chessboard. Please provide a move in the format of the starting square to the ending square, such as \"e2e4\" for a pawn move from e2 to e4.'\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "Ah, it appears we had a small oversight with whose turn it was. Since White moved pawn to e4 and Black followed with pawn to e5, it's indeed White's turn. Over to you, PlayerWhite.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: expected uci string to be of length 4 or 5: \"It's your turn to provide a move for White. Please tell me which move you'd like to make.\"\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "Ah, I see. Then let's continue this classic ballet of chess with a typical response:\n", + "\n", + "ng1f3\n", + "\n", + "Your move!\n", "\n", "--------------------------------------------------------------------------------\n" ] @@ -314,27 +453,38 @@ "text": [ "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", "\n", - "g1f3\n", + "Ah, I see. Then let's continue this classic ballet of chess with a typical response:\n", + "\n", + "ng1f3\n", + "\n", + "Your move!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", "\n", - "g1f3\n", + "Ah, I see. Then let's continue this classic ballet of chess with a typical response:\n", + "\n", + "ng1f3\n", + "\n", + "Your move!\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "d7d5\n", + "\n", + "All right, the dance continues as the pawns stride forward. Your move, PlayerWhite!\n", + "\n", + "With the following carryover: \n", + "\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", - "g1f3\n", - "Context: \n", - "Current board:\n", - "r n b q k b n r\n", - "p p p p . p p p\n", - ". . . . . . . .\n", - ". . . . p . . .\n", - ". . . . P . . .\n", - ". . . . . N . .\n", - "P P P P . P P P\n", - "R N B Q K B . R\n", + "d7d5\n", + "\n", + "All right, the dance continues as the pawns stride forward. Your move, PlayerWhite!\n", "\n", "--------------------------------------------------------------------------------\n" ] @@ -342,17 +492,17 @@ { "data": { "image/svg+xml": [ - "
r n b q k b . r\n",
-       "p p p p . p p p\n",
-       ". . . . . n . .\n",
-       ". . . . p . . .\n",
+       "
r n b q k b n r\n",
+       "p p p . . p p p\n",
+       ". . . . . . . .\n",
+       ". . . p p . . .\n",
        ". . . . P . . .\n",
        ". . . . . N . .\n",
        "P P P P . P P P\n",
-       "R N B Q K B . R
" + "R N B Q K B . R
" ], "text/plain": [ - "'
r n b q k b . r\\np p p p . p p p\\n. . . . . n . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . N . .\\nP P P P . P P P\\nR N B Q K B . R
'" + "'
r n b q k b n r\\np p p . . p p p\\n. . . . . . . .\\n. . . p p . . .\\n. . . . P . . .\\n. . . . . N . .\\nP P P P . P P P\\nR N B Q K B . R
'" ] }, "metadata": {}, @@ -364,116 +514,620 @@ "text": [ "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", "\n", - "g8f6\n", + "d7d5\n", + "\n", + "All right, the dance continues as the pawns stride forward. Your move, PlayerWhite!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", "\n", - "g8f6\n", + "d7d5\n", + "\n", + "All right, the dance continues as the pawns stride forward. Your move, PlayerWhite!\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "c2c4\n", + "\n", + "Charging forward with the queen's gambit! The board's gauntlet has been thrown; do you accept the challenge or have another plan up your sleeve, PlayerBlack?\n", + "\n", + "With the following carryover: \n", + "\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "c2c4\n", + "\n", + "Charging forward with the queen's gambit! The board's gauntlet has been thrown; do you accept the challenge or have another plan up your sleeve, PlayerBlack?\n", "\n", "--------------------------------------------------------------------------------\n" ] }, { "data": { + "image/svg+xml": [ + "
r n b q k b n r\n",
+       "p p p . . p p p\n",
+       ". . . . . . . .\n",
+       ". . . p p . . .\n",
+       ". . P . P . . .\n",
+       ". . . . . N . .\n",
+       "P P . P . P P P\n",
+       "R N B Q K B . R
" + ], "text/plain": [ - "ChatResult(chat_history=[{'content': 'Your turn.', 'role': 'assistant'}, {'content': 'e2e4', 'role': 'user'}, {'content': 'e7e5', 'role': 'assistant'}, {'content': 'g1f3', 'role': 'user'}, {'content': 'g8f6', 'role': 'assistant'}], summary='g8f6', cost=({'total_cost': 0.00885, 'gpt-4': {'cost': 0.00885, 'prompt_tokens': 229, 'completion_tokens': 33, 'total_tokens': 262}}, {'total_cost': 0}), human_input=[])" + "'
r n b q k b n r\\np p p . . p p p\\n. . . . . . . .\\n. . . p p . . .\\n. . P . P . . .\\n. . . . . N . .\\nP P . P . P P P\\nR N B Q K B . R
'" ] }, - "execution_count": 8, "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def player2board_reply(recipient, messages, sender, config):\n", - " board = config if config else \"\"\n", - " # add a system message about the current state of the board.\n", - " board_state_msg = [{\"role\": \"system\", \"content\": f\"Current board:\\n{board}\"}]\n", - " last_message = messages[-1]\n", - " if last_message[\"content\"].startswith(\"Error\"):\n", - " # try again\n", - " # last_message[\"role\"] = \"system\"\n", - " # return True, messages + board_state_msg\n", - " # TODO: better way to handle this\n", - " _, rep = recipient.generate_oai_reply(messages + board_state_msg, sender)\n", - " return True, rep\n", - " else:\n", - " return True, None\n", - "\n", - "\n", - "def board_reply(recipient, messages, sender, config):\n", - " # print(\"resplyssss\")\n", - " if recipient.reply_num >= max_turn:\n", - " return True, None\n", - " message = messages[-1]\n", - " # extract a UCI move from player's message\n", - " # TODO: better way to handle this\n", - " _, reply = recipient.generate_oai_reply(recipient.correct_move_messages[sender] + [message], sender)\n", - " # reply = messages[-1][\"content\"]\n", - " uci_move = reply if isinstance(reply, str) else str(reply[\"content\"])\n", - " recipient.update_reply_num()\n", - " try:\n", - " recipient.board.push_uci(uci_move)\n", - " except ValueError as e:\n", - " # invalid move\n", - " return True, f\"Error: {e}\"\n", - " else:\n", - " # valid move\n", - " m = chess.Move.from_uci(uci_move)\n", - " try:\n", - " display( # noqa: F821\n", - " chess.svg.board(\n", - " recipient.board, arrows=[(m.from_square, m.to_square)], fill={m.from_square: \"gray\"}, size=200\n", - " )\n", - " )\n", - " except NameError as e:\n", - " print(f\"Error displaying board: {e}\")\n", - " # better way to handle this\n", - " recipient.set_correct_move_messages(sender, message, uci_move)\n", - " recipient.correct_move_messages[sender][-1][\"role\"] = \"assistant\"\n", - " return True, uci_move\n", - "\n", - "\n", - "def board_chat_func(chat_queue, recipient, messages, sender, config):\n", - " # TODO: better way to handle this\n", - " if chat_queue[0][\"recipient\"].reply_num >= max_turn:\n", - " return True, None\n", - " msg_content = messages[-1].get(\"content\", \"\")\n", - " # board = config\n", - " board = chat_queue[0][\"recipient\"].board\n", - " # if msg_content.startswith(\"Error\"):\n", - " if msg_content:\n", - " # TODO: how to handle message if there are multiple chats (doing this for the first chat for now)\n", - " c = chat_queue[0]\n", - " c[\"message\"] = msg_content\n", - " c[\"carryover\"] = f\"Current board:\\n{board}\" # NOTE: in the old code, this is added as system message\n", - "\n", - " chat_queue[0] = c\n", - " res = recipient.initiate_chats(chat_queue)\n", - " last_res = list(res.values())[-1]\n", - " return True, last_res.summary\n", - " else:\n", - " return True, None\n", - "\n", - "\n", - "board_agent.register_reply(autogen.Agent, board_reply, 0, ignore_async_in_sync_chat=True)\n", - "player_white.register_nested_chats(\n", - " [autogen.Agent, None],\n", - " [{\"recipient\": board_agent, \"summary_method\": \"last_msg\"}],\n", - " board_chat_func,\n", - " 0,\n", - " ignore_async_in_sync_chat=True,\n", - ")\n", - "player_black.register_nested_chats(\n", - " [autogen.Agent, None],\n", + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "c2c4\n", + "\n", + "Charging forward with the queen's gambit! The board's gauntlet has been thrown; do you accept the challenge or have another plan up your sleeve, PlayerBlack?\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", + "\n", + "c2c4\n", + "\n", + "Charging forward with the queen's gambit! The board's gauntlet has been thrown; do you accept the challenge or have another plan up your sleeve, PlayerBlack?\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "e7e6\n", + "\n", + "I see you're in the mood for a classic opening, PlayerWhite! Let's keep it civil with a move straight out of the textbook. Your turn!\n", + "\n", + "With the following carryover: \n", + "\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\n", + "e7e6\n", + "\n", + "I see you're in the mood for a classic opening, PlayerWhite! Let's keep it civil with a move straight out of the textbook. Your turn!\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "\n", + "Error: illegal uci: 'e7e6' in rnbqkbnr/ppp2ppp/8/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R b KQkq - 0 3\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\n", + "My apologies, it seems there was a mistake in the move communication. It's your move, PlayerBlack. The board is all set for an interesting game ahead!\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "\n", + "Error: expected uci string to be of length 4 or 5: \"I need a specific move in standard chess notation (e.g., e2e4, g8f6, etc.) to convert it into a UCI move. Please provide me with the move you'd like to make.\"\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\n", + "e2e3\n", + "\n", + "Just keeping things flexible, PlayerBlack! It's your move – let's see where this symphony of pawns and pieces takes us.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "\n", + "Error: illegal uci: 'e2e3' in rnbqkbnr/ppp2ppp/8/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R b KQkq - 0 3\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\n", + "My mistake, PlayerBlack. The board is set, and it is indeed your move. Please proceed with your next chess move.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "\n", + "Error: expected uci string to be of length 4 or 5: 'I am sorry, but you did not provide a specific move to translate into UCI format. Please tell me the move you would like to make in terms of the starting and ending squares, such as \"e2 to e4\" or \"knight from b1 to c3\".'\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\n", + "It looks like it's your move, PlayerBlack. The board is all set and the game is ongoing. Please provide your next move.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "\n", + "Error: expected uci string to be of length 4 or 5: 'To provide you with a UCI move, I need to know the actual move you wish to make. Please tell me the piece you\\'d like to move and its destination square, such as \"e7 to e5\".'\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\n", + "It's your turn, PlayerBlack. Please proceed with your move using the standard chess notation. For example, if you want to move a pawn from d7 to d6, you would say \"d7d6\". Looking forward to seeing your strategy unfold!\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "\n", + "Error: expected uci string to be of length 4 or 5: 'Your message does not contain a move for me to translate. Please provide a specific move in standard chess notation.'\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\n", + "Oh, it's your move, PlayerBlack. The board is ready for your next play. Please make your next move in standard chess notation (e.g., e7e5, g8f6, etc.).\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "\n", + "Error: expected uci string to be of length 4 or 5: 'You have not provided a specific move, just examples of standard chess notation. To generate a UCI move, I need you to tell me the exact move you want to make, like \"pawn to e5\" or \"knight to f6\". Please provide a specific move.'\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\n", + "It's still your move, PlayerBlack. Looking at the board, it's clear you have several options to develop your pieces. When you're ready, please provide your move.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "\n", + "Error: expected uci string to be of length 4 or 5: \"I'm sorry, but as an AI, I need specific details on the current state of the game or a specific move communicated to me in order to generate a UCI move string. Could you please provide the move you would like to make?\"\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\n", + "It seems I made an error previously in parsing the game state. PlayerBlack, it appears to be your move, with the position as follows:\n", + "\n", + "```\n", + "r n b q k b n r\n", + "p p p . . p p p\n", + ". . . . . . . .\n", + ". . . p p . . .\n", + ". . P . P . . .\n", + ". . . . . N . .\n", + "P P . P . P P P\n", + "R N B Q K B . R\n", + "```\n", + "\n", + "Now that the board state is clear, please make your next move using standard algebraic notation (e.g., e7e5, Ng8f6, etc.), and I'll communicate it forward in UCI format.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "\n", + "Error: illegal uci: 'e7e5' in rnbqkbnr/ppp2ppp/8/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R b KQkq - 0 3\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\n", + "Indeed, the move e7e5 is illegal given the current position since there is a pawn on d5. PlayerBlack, please make a legal move considering the current board state.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "\n", + "Error: illegal uci: 'd7d5' in rnbqkbnr/ppp2ppp/8/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R b KQkq - 0 3\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\n", + "It appears there's some confusion with the moves. Let me clarify the board position for both of us. As of now, there's a pawn on d5, so it cannot be moved to d5 again.\n", + "\n", + "PlayerBlack, considering the position, it looks like your pawns on e6 and c7 are free to move, or perhaps you could develop one of your knights. Please provide a legal move.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "\n", + "Error: illegal uci: 'e7e6' in rnbqkbnr/ppp2ppp/8/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R b KQkq - 0 3\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\n", + "Oh, looks like we had a bit of miscommunication. You can't move the e7 pawn to e6 since you already have a pawn there. The board indicates that your previous move was d7 to d5. Perhaps you might consider developing your knights or your other pieces. Please provide a different legal move, PlayerBlack.\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "data": { + "image/svg+xml": [ + "
r n b q k b . r\n",
+       "p p p . . p p p\n",
+       ". . . . . n . .\n",
+       ". . . p p . . .\n",
+       ". . P . P . . .\n",
+       ". . . . . N . .\n",
+       "P P . P . P P P\n",
+       "R N B Q K B . R
" + ], + "text/plain": [ + "'
r n b q k b . r\\np p p . . p p p\\n. . . . . n . .\\n. . . p p . . .\\n. . P . P . . .\\n. . . . . N . .\\nP P . P . P P P\\nR N B Q K B . R
'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "\n", + "Oh, looks like we had a bit of miscommunication. You can't move the e7 pawn to e6 since you already have a pawn there. The board indicates that your previous move was d7 to d5. Perhaps you might consider developing your knights or your other pieces. Please provide a different legal move, PlayerBlack.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", + "\n", + "Oh, looks like we had a bit of miscommunication. You can't move the e7 pawn to e6 since you already have a pawn there. The board indicates that your previous move was d7 to d5. Perhaps you might consider developing your knights or your other pieces. Please provide a different legal move, PlayerBlack.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "Ah, the battlefield of mind is heating up already. We certainly can't have two pawns on the same square, now can we? In that case, let's get some more forces into the fray. How about I move my knight from b8 to c6? That will add a little support to my d5 pawn.\n", + "\n", + "So, here's my move: Nb8c6. Let the game continue!\n", + "\n", + "With the following carryover: \n", + "\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "Ah, the battlefield of mind is heating up already. We certainly can't have two pawns on the same square, now can we? In that case, let's get some more forces into the fray. How about I move my knight from b8 to c6? That will add a little support to my d5 pawn.\n", + "\n", + "So, here's my move: Nb8c6. Let the game continue!\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: illegal uci: 'b8c6' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "My apologies for the confusion; it seems I made a slight error in my communication. Instead of knight to c6, I'm going to make a more modest move to ensure we're progressing with legal plays. I'll move my knight to d7 to support my pawn a bit more.\n", + "\n", + "Here's my move: Nb8d7. Your move, PlayerWhite!\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: invalid uci: 'nb8d7'\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "Ah, I see my mistake. Let's correct that! I'll get that knight into the game properly now. I'm moving my knight from g8 to f6.\n", + "\n", + "Here's the corrected move: Ng8f6. It's up to you now, PlayerWhite. Let's see what you have planned!\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: illegal uci: 'g8f6' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "Whoops! It seems I got ahead of myself. The knight on g8 has already been repositioned. Let me recalibrate my strategy here.\n", + "\n", + "In that case, the move will be to develop my dark-squared bishop by moving it to e7:\n", + "\n", + "Move: Bf8e7\n", + "\n", + "A quiet move for a silent attack. Your turn, PlayerWhite!\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: illegal uci: 'f8e7' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "Thank you for your patience. It's time to make a move that abides by the board's current state.\n", + "\n", + "It's my move as black, so let's strike a central note. I'll move my pawn from d5 to e4, challenging the center directly.\n", + "\n", + "Here's the official notation: d5e4.\n", + "\n", + "A pawn exchange might be in the future! What do you say, PlayerWhite?\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: illegal uci: 'd5e4' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "My apologies once again for the confusion. Let's ensure we have the right move this time. I see now that it is your move, PlayerWhite. Please proceed with your move, and then I'll respond accordingly.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: illegal uci: 'e2e4' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "Understood, it looks like we've had a little mix-up, but now we're back on track. It's your move, PlayerWhite, as you have the white pieces and it's your turn. Once you've made your move, I'll respond as Black. Let the game continue.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: expected uci string to be of length 4 or 5: 'Your message does not contain a specific chess move. Please tell me your move in standard algebraic notation or describe the move you would like to make, and I will translate it into a UCI move string.'\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "I apologize for the confusion earlier. Now that we have clarified whose turn it is, you may proceed with your move as PlayerWhite. Once you have made your move, I will respond accordingly as PlayerBlack. Your move, PlayerWhite.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: illegal uci: 'e2e4' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "The current board position reflects that the last move made was e4 from PlayerWhite. As PlayerBlack, it is now my turn to make a move. I am going to develop my bishop to e7.\n", + "\n", + "Here's my move: Bf8e7\n", + "\n", + "Now it's your turn, PlayerWhite. Let's see how you will carry on the battle from here!\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: illegal uci: 'f8e7' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "My apologies for the confusion earlier. It appears to be my turn as PlayerBlack.\n", + "\n", + "For my move, I'll develop my bishop to e6 to support my center pawn.\n", + "\n", + "Here's my move: Bf8e6\n", + "\n", + "Your board, PlayerWhite. How will you respond to this development?\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: illegal uci: 'f8e6' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "It seems there has been a bit of confusion, but no worries. PlayerWhite, since you have the move and the pieces are all in their initial positions except for the pawn move to e4 and my knight move to f6, I await your move. Please proceed, PlayerWhite.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: illegal uci: 'e2e4' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "I acknowledge the current board state with your e4 move and my knight already positioned at f6. As it is your move, PlayerWhite, please go ahead and I'll respond thereafter.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: illegal uci: 'e2e4' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "My deepest apologies for the oversight on the current position earlier. It is indeed your move as white, PlayerWhite.\n", + "\n", + "Considering the current board setup, it is clear that both players have only made a single pawn move to e4 and e5 respectively, and white's knight is developed to f3, whereas black's knight has been played to f6.\n", + "\n", + "Now, it is white's turn. Please make your next move, PlayerWhite.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: illegal uci: 'e4e5' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "Thank you for the update. It appears we had a miscommunication about the board state. Now, with the correct position in front of us, I see that it is indeed your move as White.\n", + "\n", + "With your pieces poised and ready, please proceed with your next move, PlayerWhite.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: expected uci string to be of length 4 or 5: 'I\\'m sorry, but you haven\\'t provided any specific move to translate into UCI format. Please provide the move you\\'d like to make, such as \"pawn from e2 to e4\" or \"knight from g1 to f3\", and I\\'ll translate it for you.'\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "Understood. We are now clear on the board state. As it appears to be White's turn, I will await your move, PlayerWhite. Please provide your move in standard UCI format when you are ready.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: expected uci string to be of length 4 or 5: 'Your message did not include a specific move in UCI format for me to extract. Please provide a move in natural language or direct UCI format for me to translate.'\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "Ah, now that we're on the same page about the board state, it's your move, PlayerWhite. Once you make your move, I'll plan my response. The game awaits your play! Shall we venture forth into the middlegame?\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: illegal uci: 'e2e4' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "Acknowledging the board state, it's White's turn. PlayerWhite, I await your move. The pieces are in tension, eager to advance. What shall be your strategy? Proceed with your move.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: expected uci string to be of length 4 or 5: 'I need specific details of the move you want to make in order to provide a UCI move string. Please tell me the move you\\'d like to make, such as \"Move pawn from e2 to e4\" or \"Knight from g1 to f3.\"'\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "As the board currently stands, it is indeed White's (your) move. I'm looking forward to seeing how you plan to shape the game from here. Please make your move, PlayerWhite.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: illegal uci: 'e2e4' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "Ah, now that the board state is clear and it's your move as white, PlayerWhite, let's see how you'll advance the pieces. Take your time and make your play.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: expected uci string to be of length 4 or 5: 'Your instruction does not contain specific details on the move to be made. Please provide the move you would like translated into UCI format. For instance, \"move the pawn from e2 to e4\" or \"knight from g1 to f3\".'\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "It appears there has been a continuous miscommunication regarding the moves. However, based on our current understanding of the board, it is indeed PlayerWhite's turn.\n", + "\n", + "To reiterate, the only moves that have been made are:\n", + "\n", + "1. White has moved their pawn to e4.\n", + "2. Black has moved their pawn to e5.\n", + "3. White has moved their knight to f3.\n", + "4. Black has moved their knight to f6.\n", + "\n", + "Now the board is set, and it's your move, PlayerWhite. Could you please provide your move in the universal chess interface (UCI) format, which should consist of four characters (e.g., \"e2e4\")? If you choose to castle, it should be written as \"e1g1\" for kingside or \"e1c1\" for queenside. Please proceed with your move.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: illegal uci: 'e4e5' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\n", + "With the current state of the board in front of us, it is indeed White's move. PlayerWhite, please make your move, and I will then respond as Black.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", + "\n", + "With the current state of the board in front of us, it is indeed White's move. PlayerWhite, please make your move, and I will then respond as Black.\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "data": { + "text/plain": [ + "ChatResult(chat_history=[{'content': 'Your turn.', 'role': 'assistant'}, {'content': \"Ah, my apologies, it's your move to set the stage. Take it away, PlayerWhite!\", 'role': 'user'}, {'content': \"The board indicates that my first move, e4, has been played. It's your turn PlayerBlack. Please make your move.\", 'role': 'assistant'}, {'content': \"Ah, I see. Then let's continue this classic ballet of chess with a typical response:\\n\\nng1f3\\n\\nYour move!\", 'role': 'user'}, {'content': 'd7d5\\n\\nAll right, the dance continues as the pawns stride forward. Your move, PlayerWhite!', 'role': 'assistant'}, {'content': \"c2c4\\n\\nCharging forward with the queen's gambit! The board's gauntlet has been thrown; do you accept the challenge or have another plan up your sleeve, PlayerBlack?\", 'role': 'user'}, {'content': \"Oh, looks like we had a bit of miscommunication. You can't move the e7 pawn to e6 since you already have a pawn there. The board indicates that your previous move was d7 to d5. Perhaps you might consider developing your knights or your other pieces. Please provide a different legal move, PlayerBlack.\", 'role': 'assistant'}, {'content': \"With the current state of the board in front of us, it is indeed White's move. PlayerWhite, please make your move, and I will then respond as Black.\", 'role': 'user'}], summary=\"With the current state of the board in front of us, it is indeed White's move. PlayerWhite, please make your move, and I will then respond as Black.\", cost=({'total_cost': 1.61226, 'gpt-4': {'cost': 1.61226, 'prompt_tokens': 48530, 'completion_tokens': 2606, 'total_tokens': 51136}}, {'total_cost': 1.5953100000000002, 'gpt-4': {'cost': 1.5953100000000002, 'prompt_tokens': 48055, 'completion_tokens': 2561, 'total_tokens': 50616}}), human_input=[])" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def player2board_reply(recipient, messages, sender, config):\n", + " board = config if config else \"\"\n", + " # add a system message about the current state of the board.\n", + " board = sender.board\n", + " board_state_msg = [{\"role\": \"system\", \"content\": f\"Current board:\\n{board}\"}]\n", + " last_message = messages[-1]\n", + " if last_message[\"content\"].startswith(\"Error\"):\n", + " # try again\n", + " _, rep = recipient.generate_oai_reply(messages + board_state_msg, sender)\n", + " # rep = recipient.generate_reply(messages + board_state_msg, sender)\n", + " return True, rep\n", + " else:\n", + " return True, None\n", + "\n", + "\n", + "def board_chat_func(chat_queue, recipient, messages, sender, config):\n", + " # TODO: better way to handle this\n", + " if chat_queue[0][\"recipient\"].reply_num >= max_turn:\n", + " return True, None\n", + " c = chat_queue[0] # board = config\n", + " board = c[\"recipient\"].board\n", + " board_state_msg = [{\"role\": \"system\", \"content\": f\"Current board:\\n{board}\"}]\n", + " useful_msg = messages[-1].copy()\n", + " useful_msg[\"content\"] = useful_msg.get(\"content\", \"\") + f\"The current board is:\\n {board} .\"\n", + " # oai_messages = [messages[-1]]\n", + " # messages[-1] = useful_msg\n", + " _, message = recipient.generate_oai_reply([useful_msg] + board_state_msg, sender)\n", + " c[\"message\"] = message\n", + " # c[\"carryover\"] = f\"Current board:\\n{board}\" # NOTE: in the old code, this is added as system message\n", + " chat_queue[0] = c\n", + " # print(\"nessss\")\n", + " res = recipient.initiate_chats(chat_queue)\n", + " last_res = list(res.values())[-1]\n", + " return True, last_res.summary\n", + "\n", + "\n", + "def board_reply(recipient, messages, sender, config):\n", + " # print(\"resplyssss\")\n", + " if recipient.reply_num >= max_turn:\n", + " return True, None\n", + " org_msg = messages[-1].copy()\n", + " message = messages[-1]\n", + " # extract a UCI move from player's message\n", + " # TODO: better way to handle this\n", + " message[\"content\"] = \"Extract a UCI move from the following message \\n.\" + message.get(\"content\", \"\")\n", + " _, reply = recipient.generate_oai_reply([message], sender)\n", + " uci_move = reply if isinstance(reply, str) else str(reply[\"content\"])\n", + " recipient.update_reply_num()\n", + " try:\n", + " recipient.board.push_uci(uci_move)\n", + " except ValueError as e:\n", + " # invalid move\n", + " return True, f\"Error: {e}\"\n", + " else:\n", + " # valid move\n", + " m = chess.Move.from_uci(uci_move)\n", + " try:\n", + " display( # noqa: F821\n", + " chess.svg.board(\n", + " recipient.board, arrows=[(m.from_square, m.to_square)], fill={m.from_square: \"gray\"}, size=200\n", + " )\n", + " )\n", + " except NameError as e:\n", + " print(f\"Error displaying board: {e}\")\n", + " # better way to handle this\n", + " recipient.set_correct_move_messages(sender, message, uci_move)\n", + " recipient.correct_move_messages[sender][-1][\"role\"] = \"assistant\"\n", + " return True, org_msg.get(\"content\", \"\") # + \"\\n Move:\" + uci_move\n", + "\n", + "\n", + "board_agent.register_reply([white_player_name, black_player_name], board_reply, 0)\n", + "player_white.register_nested_chats(\n", + " black_player_name,\n", + " [{\"recipient\": board_agent, \"summary_method\": \"last_msg\"}],\n", + " board_chat_func,\n", + " 0,\n", + " ignore_async_in_sync_chat=True,\n", + ")\n", + "player_black.register_nested_chats(\n", + " white_player_name,\n", " [{\"recipient\": board_agent, \"summary_method\": \"last_msg\"}],\n", " board_chat_func,\n", " 0,\n", " ignore_async_in_sync_chat=True,\n", ")\n", - "player_white.register_reply(BoardAgent, player2board_reply, ignore_async_in_sync_chat=True)\n", - "player_black.register_reply(BoardAgent, player2board_reply, ignore_async_in_sync_chat=True)\n", + "player_white.register_reply(BoardAgent, player2board_reply)\n", + "player_black.register_reply(BoardAgent, player2board_reply)\n", "\n", "player_white.initiate_chat(player_black, message=\"Your turn.\")" ] From 4ab3d64013b03b0439643e9fb14dccfcf8f8b22d Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sat, 10 Feb 2024 21:13:14 -0500 Subject: [PATCH 12/57] update notebook --- notebook/agentchat_nestedchat_optiguide.ipynb | 64 ++----------------- 1 file changed, 4 insertions(+), 60 deletions(-) diff --git a/notebook/agentchat_nestedchat_optiguide.ipynb b/notebook/agentchat_nestedchat_optiguide.ipynb index 0abd332391c..5fde5c59b0c 100644 --- a/notebook/agentchat_nestedchat_optiguide.ipynb +++ b/notebook/agentchat_nestedchat_optiguide.ipynb @@ -512,9 +512,6 @@ "What if we prohibit shipping from supplier 1 to roastery 2?\n", "\n", "--------------------------------------------------------------------------------\n", - "ssssss \n", - "ssssssxxxxx (False, None)\n", - "ssssss functools.partial(, [{'recipient': <__main__.Writer object at 0x1767bfb80>, 'summary_method': 'last_msg'}])\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", "\u001b[34mStart a new chat with the following message: \n", @@ -533,16 +530,6 @@ "\n", "\n", "--------------------------------------------------------------------------------\n", - "ssssss \n", - "ssssssxxxxx (False, None)\n", - "ssssss \n", - "ssssssxxxxx (False, None)\n", - "ssssss \n", - "ssssssxxxxx (False, None)\n", - "ssssss \n", - "ssssssxxxxx (False, None)\n", - "ssssss \n", - "ssssssxxxxx (True, '```python\\nmodel.addConstr(x[\\'supplier1\\', \\'roastery2\\'] == 0, \"prohibit_s1_r2\")\\n```')\n", "\u001b[33mwriter\u001b[0m (to commander):\n", "\n", "```python\n", @@ -550,9 +537,6 @@ "```\n", "\n", "--------------------------------------------------------------------------------\n", - "ssssss \n", - "ssssssxxxxx (False, None)\n", - "ssssss functools.partial(, [{'recipient': , 'summary_method': 'last_msg'}])\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", "\u001b[34mStart a new chat with the following message: \n", @@ -797,38 +781,16 @@ "\n", "\n", "--------------------------------------------------------------------------------\n", - "ssssss \n", - "ssssssxxxxx (False, None)\n", - "ssssss \n", - "ssssssxxxxx (False, None)\n", - "ssssss \n", - "ssssssxxxxx (False, None)\n", - "ssssss \n", - "ssssssxxxxx (False, None)\n", - "ssssss \n", - "ssssssxxxxx (True, 'SAFE')\n", "\u001b[33msafeguard\u001b[0m (to commander):\n", "\n", "SAFE\n", "\n", "--------------------------------------------------------------------------------\n", - "ssssss \n", - "ssssssxxxxx (False, None)\n", - "ssssss \n", - "ssssssxxxxx (False, None)\n", - "ssssss \n", - "ssssssxxxxx (False, None)\n", - "ssssss \n", - "ssssssxxxxx (False, None)\n", - "ssssss \n", - "ssssssxxxxx (True, 'TERMINATE')\n", "\u001b[33mcommander\u001b[0m (to safeguard):\n", "\n", "TERMINATE\n", "\n", "--------------------------------------------------------------------------------\n", - "ssssss \n", - "ssssssxxxxx (True, None)\n", "Restricted license - for non-production use only - expires 2025-11-24\n", "Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n", "\n", @@ -836,7 +798,7 @@ "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0xab8d681d\n", + "Model fingerprint: 0x87036b63\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", @@ -869,7 +831,7 @@ "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 12 rows, 18 columns and 37 nonzeros\n", - "Model fingerprint: 0x5dab87a5\n", + "Model fingerprint: 0xe577deca\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", @@ -900,10 +862,9 @@ "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 2.760000000000e+03, best bound 2.760000000000e+03, gap 0.0000%\n", - "Fri Feb 9 23:30:15 2024\n", + "Fri Feb 9 23:39:06 2024\n", "Optimal cost: 2760.0\n", "\u001b[33mOptimization problem solved. The objective value is: 2760.0\u001b[0m\n", - "ssssssxxxxx (True, 'Here are the execution results: Optimization problem solved. The objective value is: 2760.0\\n\\nCan you organize these information to a human readable answer?\\nRemember to compare the new results to the original results you obtained in the\\nbeginning.\\n\\n--- HUMAN READABLE ANSWER ---\\n')\n", "\u001b[33mcommander\u001b[0m (to writer):\n", "\n", "Here are the execution results: Optimization problem solved. The objective value is: 2760.0\n", @@ -916,16 +877,6 @@ "\n", "\n", "--------------------------------------------------------------------------------\n", - "ssssss \n", - "ssssssxxxxx (False, None)\n", - "ssssss \n", - "ssssssxxxxx (False, None)\n", - "ssssss \n", - "ssssssxxxxx (False, None)\n", - "ssssss \n", - "ssssssxxxxx (False, None)\n", - "ssssss \n", - "ssssssxxxxx (True, 'Certainly, the results from optimizing the supply chain model suggest the following:\\n\\nAfter adding the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was still successfully solved. The new objective value, which represents the total cost of the supply chain operation after implementing the constraint, is $2760.0.\\n\\nTo provide a comparison with the original results, we should look at the objective value before the constraint was added. If the previous objective value is lower than $2760.0, this implies that the new constraint increased the total cost of operations, highlighting the economic impact of not using supplier 1 for roastery 2.\\n\\nOn the other hand, if the previous objective value is higher than $2760.0, then despite the constraint, we have found a more cost-effective way to meet the supply and demand needs.\\n\\nUnfortunately, the original objective value prior to this constraint being added is not provided here. For a full comparison, you would need that original value to assess the financial impact of this constraint on the overall supply chain cost.\\n\\n--- HUMAN READABLE ANSWER ---\\nThe new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.')\n", "\u001b[33mwriter\u001b[0m (to commander):\n", "\n", "Certainly, the results from optimizing the supply chain model suggest the following:\n", @@ -942,11 +893,6 @@ "The new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.\n", "\n", "--------------------------------------------------------------------------------\n", - "ssssss \n", - "ssssssxxxxx (False, None)\n", - "ssssss functools.partial(, [{'recipient': , 'summary_method': 'last_msg', 'message': '\\nGiven the original source code:\\nimport time\\n\\nfrom gurobipy import GRB, Model\\n\\n# Example data\\n\\ncapacity_in_supplier = {\\'supplier1\\': 150, \\'supplier2\\': 50, \\'supplier3\\': 100}\\n\\nshipping_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\\nroasting_cost_light = {\\'roastery1\\': 3, \\'roastery2\\': 5}\\n\\nroasting_cost_dark = {\\'roastery1\\': 5, \\'roastery2\\': 6}\\n\\nshipping_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\\nlight_coffee_needed_for_cafe = {\\'cafe1\\': 20, \\'cafe2\\': 30, \\'cafe3\\': 40}\\n\\ndark_coffee_needed_for_cafe = {\\'cafe1\\': 20, \\'cafe2\\': 20, \\'cafe3\\': 100}\\n\\ncafes = list(set(i[1] for i in shipping_cost_from_roastery_to_cafe.keys()))\\nroasteries = list(\\n set(i[1] for i in shipping_cost_from_supplier_to_roastery.keys()))\\nsuppliers = list(\\n set(i[0] for i in shipping_cost_from_supplier_to_roastery.keys()))\\n\\n# Create a new model\\nmodel = Model(\"coffee_distribution\")\\n\\n# OPTIGUIDE DATA CODE GOES HERE\\n\\n# Create variables\\nx = model.addVars(shipping_cost_from_supplier_to_roastery.keys(),\\n vtype=GRB.INTEGER,\\n name=\"x\")\\ny_light = model.addVars(shipping_cost_from_roastery_to_cafe.keys(),\\n vtype=GRB.INTEGER,\\n name=\"y_light\")\\ny_dark = model.addVars(shipping_cost_from_roastery_to_cafe.keys(),\\n vtype=GRB.INTEGER,\\n name=\"y_dark\")\\n\\n# Set objective\\nmodel.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\\nfor r in set(i[1] for i in shipping_cost_from_supplier_to_roastery.keys()):\\n model.addConstr(\\n sum(x[i] 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\\nfor s in set(i[0] for i in shipping_cost_from_supplier_to_roastery.keys()):\\n model.addConstr(\\n sum(x[i] 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\\nfor c in set(i[1] for i in shipping_cost_from_roastery_to_cafe.keys()):\\n model.addConstr(\\n sum(y_light[j] 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] for j in shipping_cost_from_roastery_to_cafe.keys()\\n if j[1] == c) >= dark_coffee_needed_for_cafe[c],\\n f\"dark_demand_{c}\")\\n\\n# Optimize model\\nmodel.optimize()\\nm = model\\n\\n# OPTIGUIDE CONSTRAINT CODE GOES HERE\\n\\n# Solve\\nm.update()\\nmodel.optimize()\\n\\nprint(time.ctime())\\nif m.status == GRB.OPTIMAL:\\n print(f\\'Optimal cost: {m.objVal}\\')\\nelse:\\n print(\"Not solved to optimality. Optimization status:\", m.status)\\n\\n\\nIs the following code safe (not malicious code to break security\\nand privacy) to run?\\nAnswer only one word.\\nIf not safe, answer `DANGER`; else, answer `SAFE`.\\n', 'carryover': []}])\n", - "ssssssxxxxx (True, None)\n", - "ssssssxxxxx (True, 'Certainly, the results from optimizing the supply chain model suggest the following:\\n\\nAfter adding the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was still successfully solved. The new objective value, which represents the total cost of the supply chain operation after implementing the constraint, is $2760.0.\\n\\nTo provide a comparison with the original results, we should look at the objective value before the constraint was added. If the previous objective value is lower than $2760.0, this implies that the new constraint increased the total cost of operations, highlighting the economic impact of not using supplier 1 for roastery 2.\\n\\nOn the other hand, if the previous objective value is higher than $2760.0, then despite the constraint, we have found a more cost-effective way to meet the supply and demand needs.\\n\\nUnfortunately, the original objective value prior to this constraint being added is not provided here. For a full comparison, you would need that original value to assess the financial impact of this constraint on the overall supply chain cost.\\n\\n--- HUMAN READABLE ANSWER ---\\nThe new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.')\n", "\u001b[33mcommander\u001b[0m (to user):\n", "\n", "Certainly, the results from optimizing the supply chain model suggest the following:\n", @@ -962,9 +908,7 @@ "--- HUMAN READABLE ANSWER ---\n", "The new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.\n", "\n", - "--------------------------------------------------------------------------------\n", - "ssssss \n", - "ssssssxxxxx (True, None)\n" + "--------------------------------------------------------------------------------\n" ] }, { From 14ca65fab39f10a56b3dcd66a9b8d7f18d7c3822 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sun, 11 Feb 2024 02:51:13 -0500 Subject: [PATCH 13/57] summary method --- autogen/agentchat/conversable_agent.py | 23 ++++-- test/agentchat/test_chats.py | 9 ++- test/agentchat/test_nested.py | 99 ++++++++++++-------------- 3 files changed, 71 insertions(+), 60 deletions(-) diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index e00ded9210c..7a80547c9d9 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -176,6 +176,7 @@ def __init__( self._default_auto_reply = default_auto_reply self._reply_func_list = [] self._ignore_async_func_in_sync_chat_list = [] + self._exclude_reply_list = [] self._human_input = [] self.reply_at_receive = defaultdict(bool) self.register_reply([Agent, None], ConversableAgent.generate_oai_reply) @@ -274,9 +275,9 @@ def simple_chat_reply(chat_queue, recipient, messages, sender, config): return True, None else: # TODO: how to handle message if there are multiple chats (doing this for the first chat for now) - c = chat_queue[0] - c["message"] = msg_content - chat_queue[0] = c + first_chat_info = chat_queue[0] + first_chat_info["message"] = msg_content + "\n" + first_chat_info.get("message", "") + chat_queue[0] = first_chat_info res = recipient.initiate_chats(chat_queue) last_res = list(res.values())[-1] return True, last_res.summary @@ -323,6 +324,7 @@ def chat_reply_func( self.register_reply( trigger, reply_func, position, config, reset_config, ignore_async_in_sync_chat=ignore_async_in_sync_chat ) + # self._exclude_reply_list.append(reply_func) @property def system_message(self) -> Union[str, List]: @@ -874,7 +876,12 @@ def _summarize_chat( """Get a chat summary from an agent participating in a chat. Args: - method (str): the method to get the summary. + method (str or callable): the method to get the summary. + The callable method should take the agent as input and return a summary. E.g, + ```python + def get_summary(agent): + return agent.last_message(agent)["content"] + ``` agent: the participating agent in a chat. prompt (str): the prompt used to get a summary when summary_method is "reflection_with_llm". @@ -897,6 +904,14 @@ def _summarize_chat( summary = agent.last_message(self)["content"].replace("TERMINATE", "") except (IndexError, AttributeError) as e: warnings.warn(f"Cannot extract summary using last_msg: {e}", UserWarning) + elif isinstance(method, Callable): + print("methodxxxxxxxxx", method) + summary = method(self, agent) + print("susssss", summary) + # try: + # summary = method(agent) + # except Exception as e: + # warnings.warn(f"Cannot extract summary using the callable method: {e}", UserWarning) else: warnings.warn(f"Unsupported summary method: {method}", UserWarning) return summary diff --git a/test/agentchat/test_chats.py b/test/agentchat/test_chats.py index 9b578c00c70..2a6ff055e4b 100644 --- a/test/agentchat/test_chats.py +++ b/test/agentchat/test_chats.py @@ -185,6 +185,9 @@ def test_chats(): }, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly. ) + def my_last_msg(sender, receiver): + return receiver.last_message(sender)["content"].replace("TERMINATE", "") + chat_res = user.initiate_chats( [ { @@ -192,7 +195,7 @@ def test_chats(): "message": financial_tasks[0], "clear_history": True, "silent": False, - "summary_method": "last_msg", + "summary_method": my_last_msg, }, { "recipient": financial_assistant_2, @@ -281,7 +284,7 @@ def currency_calculator( if __name__ == "__main__": - # test_chats() + test_chats() # test_chats_group() # test_chats_w_func() - test_chat_messages_for_summary() + # test_chat_messages_for_summary() diff --git a/test/agentchat/test_nested.py b/test/agentchat/test_nested.py index 931fe6b0c42..9fa4ef78dfd 100644 --- a/test/agentchat/test_nested.py +++ b/test/agentchat/test_nested.py @@ -1,5 +1,6 @@ import pytest -from conftest import skip_openai + +# from conftest import skip_openai import autogen from pydantic import BaseModel, Field @@ -11,7 +12,7 @@ import chess.svg -@pytest.mark.skipif(skip_openai, reason="requested to skip openai tests") +# @pytest.mark.skipif(skip_openai, reason="requested to skip openai tests") def test_nested(): import autogen @@ -81,13 +82,17 @@ def test_nested(): user.initiate_chat(financial_assistant_1, message=financial_tasks[0]) -@pytest.mark.skipif(skip_openai, reason="requested to skip openai tests") +# @pytest.mark.skipif(skip_openai, reason="requested to skip openai tests") def test_nested_chess(): config_list_gpt4 = autogen.config_list_from_json( "OAI_CONFIG_LIST", ) + autogen.config_list_from_json( + "OAI_CONFIG_LIST", + filter_dict={"model": "gpt-35-turbo-1106"}, + ) - max_turn = 5 + max_turn = 20 sys_msg_tmpl = """Your name is {name} and you are a chess player. You are playing against {opponent_name}. @@ -98,7 +103,8 @@ def test_nested_chess(): Do not apologize for making illegal moves.""" board_sysm_msg = """You are an AI-powered chess board agent. - You translate the user's natural language input into legal UCI moves. + You translate the user's natural language input into legal UCI moves. Note that the user's input may contain the + UCI move itself, or it may contain a natural language description of the move. You should only reply with a UCI move string extracted from the user's input.""" color_white = "white" @@ -125,6 +131,7 @@ def reply_num(self): name="BoardAgent", system_message=board_sysm_msg, llm_config={"temperature": 0.0, "config_list": config_list_gpt4}, + # llm_config={"config_list": config_list_gpt4}, max_consecutive_auto_reply=max_turn, ) @@ -135,8 +142,7 @@ def reply_num(self): opponent_name=black_player_name, color=color_white, ), - llm_config={"temperature": 0.5, "cache_seed": 1, "config_list": config_list_gpt4}, - # llm_config={"config_list": config_list_gpt4}, + llm_config={"config_list": config_list_gpt4}, max_consecutive_auto_reply=max_turn, ) @@ -147,26 +153,22 @@ def reply_num(self): opponent_name=white_player_name, color=color_black, ), + # llm_config={"temperature":1, "cache_seed": 1, "config_list": config_list_gpt35}, llm_config={"config_list": config_list_gpt4}, + # llm_config={"temperature":1, "cache_seed": 1, "config_list": config_list_gpt4}, max_consecutive_auto_reply=max_turn, ) - def player2board_reply( - recipient: Optional[autogen.Agent] = None, - messages: Optional[List[Dict]] = None, - sender: Optional[autogen.Agent] = None, - config: Optional[chess.Board] = None, - ): + def player2board_reply(recipient, messages, sender, config): board = config if config else "" # add a system message about the current state of the board. + board = sender.board board_state_msg = [{"role": "system", "content": f"Current board:\n{board}"}] last_message = messages[-1] if last_message["content"].startswith("Error"): # try again - # last_message["role"] = "system" - # return True, messages + board_state_msg - # TODO: better way to handle this _, rep = recipient.generate_oai_reply(messages + board_state_msg, sender) + # rep = recipient.generate_reply(messages + board_state_msg, sender) return True, rep else: return True, None @@ -175,39 +177,35 @@ def board_chat_func(chat_queue, recipient, messages, sender, config): # TODO: better way to handle this if chat_queue[0]["recipient"].reply_num >= max_turn: return True, None - msg_content = messages[-1].get("content", "") - board = config - # if msg_content.startswith("Error"): - if msg_content: - # TODO: how to handle message if there are multiple chats (doing this for the first chat for now) - c = chat_queue[0] - c["message"] = msg_content - c["carryover"] = f"Current board:\n{board}" # NOTE: in the old code, this is added as system message - - chat_queue[0] = c - res = recipient.initiate_chats(chat_queue) - last_res = list(res.values())[-1] - return True, last_res.summary - else: - return True, None - - def board_reply( - recipient: Optional[autogen.Agent] = None, - messages: Optional[List[Dict]] = None, - sender: Optional[autogen.Agent] = None, - config: Optional[Any] = None, - ) -> Union[str, Dict, None]: + c = chat_queue[0] # board = config + board = c["recipient"].board + board_state_msg = [{"role": "system", "content": f"Current board:\n{board}"}] + useful_msg = messages[-1].copy() + useful_msg["content"] = useful_msg.get("content", "") + f"The current board is:\n {board} ." + oai_messages = [messages[-1]] + # _, message = recipient.generate_oai_reply(messages + board_state_msg, sender) + _, message = recipient.generate_oai_reply(oai_messages + board_state_msg, sender) + c["message"] = message + # c["carryover"] = f"Current board:\n{board}" # NOTE: in the old code, this is added as system message + chat_queue[0] = c + # print("nessss") + res = recipient.initiate_chats(chat_queue) + last_res = list(res.values())[-1] + # return True, message + "My Move is:\n" + last_res.summary + return True, last_res.summary + + def board_reply(recipient, messages, sender, config): # print("resplyssss") if recipient.reply_num >= max_turn: return True, None + org_msg = messages[-1].copy() message = messages[-1] # extract a UCI move from player's message # TODO: better way to handle this - _, reply = recipient.generate_oai_reply(recipient.correct_move_messages[sender] + [message], sender) - # print("recipient.correct_move_messages[sender]", recipient.correct_move_messages[sender]) - # reply = messages[-1]["content"] + message["content"] = "Extract a UCI move from the following message \n." + message.get("content", "") + _, reply = recipient.generate_oai_reply([message], sender) + # reply = recipient.generate_reply(recipient.correct_move_messages[sender] + [message], sender) uci_move = reply if isinstance(reply, str) else str(reply["content"]) - # print(f"uci_movexxxxx: {uci_move}") recipient.update_reply_num() try: recipient.board.push_uci(uci_move) @@ -228,26 +226,21 @@ def board_reply( # better way to handle this recipient.set_correct_move_messages(sender, message, uci_move) recipient.correct_move_messages[sender][-1]["role"] = "assistant" - return True, uci_move + return True, org_msg.get("content", "") # + "\n Move:" + uci_move - board_agent.register_reply(autogen.Agent, board_reply, 0, ignore_async_in_sync_chat=True) + board_agent.register_reply([white_player_name, black_player_name], board_reply, 0) player_white.register_nested_chats( - [autogen.Agent, None], + black_player_name, [{"recipient": board_agent, "summary_method": "last_msg"}], board_chat_func, - 0, - ignore_async_in_sync_chat=True, ) player_black.register_nested_chats( - [autogen.Agent, None], + white_player_name, [{"recipient": board_agent, "summary_method": "last_msg"}], board_chat_func, - 0, - ignore_async_in_sync_chat=True, ) - player_white.register_reply(BoardAgent, player2board_reply, ignore_async_in_sync_chat=True) - player_black.register_reply(BoardAgent, player2board_reply, ignore_async_in_sync_chat=True) - + player_white.register_reply(BoardAgent, player2board_reply) + player_black.register_reply(BoardAgent, player2board_reply) player_white.initiate_chat(player_black, message="Your turn.") From 64acda062c6de6b6ef7adfdd8fbd8de6abb71f91 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sun, 11 Feb 2024 13:09:46 -0500 Subject: [PATCH 14/57] initiate_chats enhancements --- autogen/agentchat/conversable_agent.py | 43 ++++++++++++++++++-------- test/agentchat/test_chats.py | 18 ++++++++--- 2 files changed, 43 insertions(+), 18 deletions(-) diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index fbba53e5e8d..05072213377 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -66,6 +66,7 @@ class ConversableAgent(LLMAgent): MAX_CONSECUTIVE_AUTO_REPLY = 100 # maximum number of consecutive auto replies (subject to future change) DEFAULT_summary_prompt = "Summarize the takeaway from the conversation. Do not add any introductory phrases." + DEFAULT_summary_method = "last_msg" llm_config: Union[Dict, Literal[False]] def __init__( @@ -792,7 +793,7 @@ def initiate_chat( self._prepare_chat(recipient, clear_history) self.send(self.generate_init_message(**context), recipient, silent=silent) summary = self._summarize_chat( - context.get("summary_method"), + context.get("summary_method", ConversableAgent.DEFAULT_summary_method), recipient, prompt=context.get("summary_prompt"), cache=cache, @@ -833,7 +834,7 @@ async def a_initiate_chat( agent.client_cache = cache await self.a_send(await self.a_generate_init_message(**context), recipient, silent=silent) summary = self._summarize_chat( - context.get("summary_method"), + context.get("summary_method", ConversableAgent.DEFAULT_summary_method), recipient, prompt=context.get("summary_prompt"), cache=cache, @@ -851,24 +852,34 @@ async def a_initiate_chat( def _summarize_chat( self, - method, - agent: Optional[Agent] = None, + summary_method, + recipient: Optional[Agent] = None, prompt: Optional[str] = None, cache: Optional[Cache] = None, ) -> str: """Get a chat summary from an agent participating in a chat. Args: - method (str): the method to get the summary. - agent: the participating agent in a chat. + summary_method (str or callable): the summary_method to get the summary. + The callable summary_method should take sender and recipient agent as input and return a string of summary. E.g, + ```python + def my_summary_method( + sender: ConversableAgent, + recipient: ConversableAgent, + ): + return recipient.last_message(sender)["content"] + ``` + recipient: the recipient agent in a chat. prompt (str): the prompt used to get a summary when summary_method is "reflection_with_llm". Returns: str: a chat summary from the agent. """ - agent = self if agent is None else agent + agent = self if recipient is None else recipient summary = "" - if method == "reflection_with_llm": + if summary_method is None: + return summary + if summary_method == "reflection_with_llm": prompt = ConversableAgent.DEFAULT_summary_prompt if prompt is None else prompt if not isinstance(prompt, str): raise ValueError("The summary_prompt must be a string.") @@ -877,13 +888,15 @@ def _summarize_chat( summary = self._reflection_with_llm(prompt, msg_list, llm_agent=agent, cache=cache) except BadRequestError as e: warnings.warn(f"Cannot extract summary using reflection_with_llm: {e}", UserWarning) - elif method == "last_msg" or method is None: + elif summary_method == "last_msg" or summary_method is None: try: summary = agent.last_message(self)["content"].replace("TERMINATE", "") except (IndexError, AttributeError) as e: warnings.warn(f"Cannot extract summary using last_msg: {e}", UserWarning) + elif isinstance(summary_method, Callable): + summary = summary_method(self, recipient) else: - warnings.warn(f"Unsupported summary method: {method}", UserWarning) + warnings.warn(f"Unsupported summary method: {summary_method}. Returning an empty summary.", UserWarning) return summary def _reflection_with_llm( @@ -944,9 +957,13 @@ def initiate_chats(self, chat_queue: List[Dict[str, Any]]) -> Dict[Agent, ChatRe for chat_info in chat_queue: assert "recipient" in chat_info, "recipient must be provided." receipts_set.add(chat_info["recipient"]) - assert len(receipts_set) == len(chat_queue), "recipients must be different." - - self._chat_queue = chat_queue + # assert len(receipts_set) == len(chat_queue), "recipients must be different." + if len(receipts_set) < len(chat_queue): + warnings.warn( + "Repetitive recipients detected: The chat history will be cleared by default if a recipient appears more than once. To retain the chat history, please set 'clear_history=False' in the configuration of the repeating agent.", + UserWarning, + ) + self._chat_queue = chat_queue.copy() self._finished_chats = {} while self._chat_queue: chat_info = self._chat_queue.pop(0) diff --git a/test/agentchat/test_chats.py b/test/agentchat/test_chats.py index 9b578c00c70..d41da7dea54 100644 --- a/test/agentchat/test_chats.py +++ b/test/agentchat/test_chats.py @@ -148,7 +148,7 @@ def test_chats(): financial_tasks = [ """What are the full names of NVDA and TESLA.""", - """Investigate the reasons.""", + """Get the current stock price of NVDA and TESLA.""", """Pros and cons of the companies I'm interested in. Keep it short.""", ] @@ -190,15 +190,21 @@ def test_chats(): { "recipient": financial_assistant_1, "message": financial_tasks[0], - "clear_history": True, "silent": False, - "summary_method": "last_msg", + "summary_method": None, }, { "recipient": financial_assistant_2, "message": financial_tasks[1], + "silent": True, "summary_method": "reflection_with_llm", }, + { + "recipient": financial_assistant_1, + "message": financial_tasks[2], + "summary_method": "last_msg", + "clear_history": False, + }, { "recipient": writer, "message": writing_tasks[0], @@ -216,6 +222,8 @@ def test_chats(): print(writer_res.summary, writer_res.cost) print(all_res[financial_assistant_1].human_input) print(all_res[financial_assistant_1].summary) + print(all_res[financial_assistant_1].chat_history) + print(all_res[financial_assistant_2].summary) # print(blogpost.summary, insights_and_blogpost) @@ -281,7 +289,7 @@ def currency_calculator( if __name__ == "__main__": - # test_chats() + test_chats() # test_chats_group() # test_chats_w_func() - test_chat_messages_for_summary() + # test_chat_messages_for_summary() From d87921ab3cb5d31520444c4c27dd00ec91d2e9fc Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sun, 11 Feb 2024 13:22:08 -0500 Subject: [PATCH 15/57] callable summary_method --- autogen/agentchat/conversable_agent.py | 4 ++-- test/agentchat/test_chats.py | 12 ++++++++---- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index 05072213377..fc290093996 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -861,7 +861,7 @@ def _summarize_chat( Args: summary_method (str or callable): the summary_method to get the summary. - The callable summary_method should take sender and recipient agent as input and return a string of summary. E.g, + The callable summary_method should take the recipient and sender agent in a chat as input and return a string of summary. E.g, ```python def my_summary_method( sender: ConversableAgent, @@ -894,7 +894,7 @@ def my_summary_method( except (IndexError, AttributeError) as e: warnings.warn(f"Cannot extract summary using last_msg: {e}", UserWarning) elif isinstance(summary_method, Callable): - summary = summary_method(self, recipient) + summary = summary_method(recipient, self) else: warnings.warn(f"Unsupported summary method: {summary_method}. Returning an empty summary.", UserWarning) return summary diff --git a/test/agentchat/test_chats.py b/test/agentchat/test_chats.py index d41da7dea54..45dafedabb6 100644 --- a/test/agentchat/test_chats.py +++ b/test/agentchat/test_chats.py @@ -148,8 +148,8 @@ def test_chats(): financial_tasks = [ """What are the full names of NVDA and TESLA.""", - """Get the current stock price of NVDA and TESLA.""", - """Pros and cons of the companies I'm interested in. Keep it short.""", + """Get their stock price.""", + """Analyze pros and cons. Keep it short.""", ] writing_tasks = ["""Develop a short but engaging blog post using any information provided."""] @@ -185,13 +185,16 @@ def test_chats(): }, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly. ) + def my_summary_method(recipient, sender): + return recipient.chat_messages[sender][0].get("content", "") + chat_res = user.initiate_chats( [ { "recipient": financial_assistant_1, "message": financial_tasks[0], "silent": False, - "summary_method": None, + "summary_method": my_summary_method, }, { "recipient": financial_assistant_2, @@ -219,6 +222,7 @@ def test_chats(): writer_res = user.get_chat_results(writer) all_res = user.get_chat_results() + assert all_res[financial_assistant_1].summary == "What are the full names of NVDA and TESLA." print(writer_res.summary, writer_res.cost) print(all_res[financial_assistant_1].human_input) print(all_res[financial_assistant_1].summary) @@ -290,6 +294,6 @@ def currency_calculator( if __name__ == "__main__": test_chats() - # test_chats_group() + test_chats_group() # test_chats_w_func() # test_chat_messages_for_summary() From f66df384f652f78f29e1ae342f37ff037aa25187 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sun, 11 Feb 2024 13:24:11 -0500 Subject: [PATCH 16/57] summary method --- autogen/agentchat/conversable_agent.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index fc290093996..5c847c052bf 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -896,7 +896,9 @@ def my_summary_method( elif isinstance(summary_method, Callable): summary = summary_method(recipient, self) else: - warnings.warn(f"Unsupported summary method: {summary_method}. Returning an empty summary.", UserWarning) + warnings.warn( + f"Unsupported summary method: {summary_method}. Returning an empty string as a summary.", UserWarning + ) return summary def _reflection_with_llm( From 69fa176c54dcbe67e134fd8a9a7bd71696a6dff9 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sun, 11 Feb 2024 13:33:50 -0500 Subject: [PATCH 17/57] summary method default --- autogen/agentchat/conversable_agent.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index 5c847c052bf..059e14c9846 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -767,7 +767,7 @@ def initiate_chat( cache (Cache or None): the cache client to be used for this conversation. **context: any context information. It has the following reserved fields: "message": a str of message. Needs to be provided. Otherwise, input() will be called to get the initial message. - "summary_method": a string specify the method to get a summary from the chat. + "summary_method": a string specifying the method to get a summary from the chat. Default is DEFAULT_summary_method, i.e., "last_msg". Supported methods are "last_msg" and "reflection_with_llm". when set "last_msg", it returns the last message of the dialog as the summary. when set "reflection_with_llm", it returns a summary extracted using an llm client. @@ -940,8 +940,8 @@ def initiate_chats(self, chat_queue: List[Dict[str, Any]]) -> Dict[Agent, ChatRe - "context": any context information, e.g., the request message. The following fields are reserved: "message" needs to be provided if the `generate_init_message` method is not overridden. Otherwise, input() will be called to get the initial message. - "summary_method" can be used to specify the method to extract a summary from the chat. - Supported methods are "last_msg" and "reflection_with_llm". + "summary_method": a string specifying the method to get a summary from the chat. Default is DEFAULT_summary_method, i.e., "last_msg". + Supported methods are "last_msg" and "reflection_with_llm". Default is "last_msg". when set "last_msg", it returns the last message of the dialog as the summary. when set "reflection_with_llm", it returns a summary extracted using an llm client. `llm_config` must be set in either the recipient or sender. @@ -959,7 +959,6 @@ def initiate_chats(self, chat_queue: List[Dict[str, Any]]) -> Dict[Agent, ChatRe for chat_info in chat_queue: assert "recipient" in chat_info, "recipient must be provided." receipts_set.add(chat_info["recipient"]) - # assert len(receipts_set) == len(chat_queue), "recipients must be different." if len(receipts_set) < len(chat_queue): warnings.warn( "Repetitive recipients detected: The chat history will be cleared by default if a recipient appears more than once. To retain the chat history, please set 'clear_history=False' in the configuration of the repeating agent.", @@ -1990,8 +1989,8 @@ def generate_init_message(self, **context) -> Union[str, Dict]: Args: **context: any context information. It has the following reserved fields: "message": a str of message. - "summary_method": a string specify the method to get a summary from the chat. - Supported methods are "last_msg" and "reflection_with_llm". + "summary_method": a string specifying the method to get a summary from the chat. Default is DEFAULT_summary_method, i.e., "last_msg". + Supported methods are "last_msg" and "reflection_with_llm". Default is "last_msg". when set "last_msg", it returns the last message of the dialog as the summary. when set "reflection_with_llm", it returns a summary extracted using an llm client. "llm" requires the llm_config to be set in either the sender or the recipient so that an llm client is available. From a6a1faa7d92a0b3396ef67c92fd05723db538ba3 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sun, 11 Feb 2024 15:04:33 -0500 Subject: [PATCH 18/57] docstr --- autogen/agentchat/conversable_agent.py | 59 ++++++++++++++++++-------- 1 file changed, 42 insertions(+), 17 deletions(-) diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index 059e14c9846..a528104024b 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -767,12 +767,20 @@ def initiate_chat( cache (Cache or None): the cache client to be used for this conversation. **context: any context information. It has the following reserved fields: "message": a str of message. Needs to be provided. Otherwise, input() will be called to get the initial message. - "summary_method": a string specifying the method to get a summary from the chat. Default is DEFAULT_summary_method, i.e., "last_msg". - Supported methods are "last_msg" and "reflection_with_llm". - when set "last_msg", it returns the last message of the dialog as the summary. - when set "reflection_with_llm", it returns a summary extracted using an llm client. - "llm" requires the llm_config to be set in either the sender or the recipient so that an llm client is available. - When both the sender and the recipient have an llm client, the recipient's llm client will be used. + "summary_method": a string or callable specifying the method to get a summary from the chat. Default is DEFAULT_summary_method, i.e., "last_msg". + - Supported string are "last_msg" and "reflection_with_llm": + when set "last_msg", it returns the last message of the dialog as the summary. + when set "reflection_with_llm", it returns a summary extracted using an llm client. + `llm_config` must be set in either the recipient or sender. + "reflection_with_llm" requires the llm_config to be set in either the sender or the recipient. + - A callable summary_method should take the recipient and sender agent in a chat as input and return a string of summary. E.g, + ```python + def my_summary_method( + sender: ConversableAgent, + recipient: ConversableAgent, + ): + return recipient.last_message(sender)["content"] + ``` "summary_prompt": a string of text used to prompt a LLM-based agent (the sender or receiver agent) to reflext on the conversation and extract a summary when summary_method is "reflection_with_llm". Default is DEFAULT_summary_prompt, i.e., "Summarize takeaway from the conversation. Do not add any introductory phrases. If the intended request is NOT properly addressed, please point it out." @@ -940,12 +948,20 @@ def initiate_chats(self, chat_queue: List[Dict[str, Any]]) -> Dict[Agent, ChatRe - "context": any context information, e.g., the request message. The following fields are reserved: "message" needs to be provided if the `generate_init_message` method is not overridden. Otherwise, input() will be called to get the initial message. - "summary_method": a string specifying the method to get a summary from the chat. Default is DEFAULT_summary_method, i.e., "last_msg". - Supported methods are "last_msg" and "reflection_with_llm". Default is "last_msg". - when set "last_msg", it returns the last message of the dialog as the summary. - when set "reflection_with_llm", it returns a summary extracted using an llm client. - `llm_config` must be set in either the recipient or sender. - "reflection_with_llm" requires the llm_config to be set in either the sender or the recipient. + "summary_method": a string or callable specifying the method to get a summary from the chat. Default is DEFAULT_summary_method, i.e., "last_msg". + - Supported string are "last_msg" and "reflection_with_llm": + when set "last_msg", it returns the last message of the dialog as the summary. + when set "reflection_with_llm", it returns a summary extracted using an llm client. + `llm_config` must be set in either the recipient or sender. + "reflection_with_llm" requires the llm_config to be set in either the sender or the recipient. + - A callable summary_method should take the recipient and sender agent in a chat as input and return a string of summary. E.g, + ```python + def my_summary_method( + sender: ConversableAgent, + recipient: ConversableAgent, + ): + return recipient.last_message(sender)["content"] + ``` "summary_prompt" can be used to specify the prompt used to extract a summary when summary_method is "reflection_with_llm". Default is None and the following default prompt will be used when "summary_method" is set to "reflection_with_llm": "Identify and extract the final solution to the originally asked question based on the conversation." @@ -1989,11 +2005,20 @@ def generate_init_message(self, **context) -> Union[str, Dict]: Args: **context: any context information. It has the following reserved fields: "message": a str of message. - "summary_method": a string specifying the method to get a summary from the chat. Default is DEFAULT_summary_method, i.e., "last_msg". - Supported methods are "last_msg" and "reflection_with_llm". Default is "last_msg". - when set "last_msg", it returns the last message of the dialog as the summary. - when set "reflection_with_llm", it returns a summary extracted using an llm client. - "llm" requires the llm_config to be set in either the sender or the recipient so that an llm client is available. + "summary_method": a string or callable specifying the method to get a summary from the chat. Default is DEFAULT_summary_method, i.e., "last_msg". + - Supported string are "last_msg" and "reflection_with_llm": + when set "last_msg", it returns the last message of the dialog as the summary. + when set "reflection_with_llm", it returns a summary extracted using an llm client. + `llm_config` must be set in either the recipient or sender. + "reflection_with_llm" requires the llm_config to be set in either the sender or the recipient. + - A callable summary_method should take the recipient and sender agent in a chat as input and return a string of summary. E.g, + ```python + def my_summary_method( + sender: ConversableAgent, + recipient: ConversableAgent, + ): + return recipient.last_message(sender)["content"] + ``` When both the sender and the recipient have an llm client, the recipient's llm client will be used. "summary_prompt": a string of text used to prompt a LLM-based agent (the sender or receiver agent) to reflext on the conversation and extract a summary when summary_method is "reflection_with_llm". From 7125c904b00dd44294dd1ec9cadb2a6eb7d57b74 Mon Sep 17 00:00:00 2001 From: Eric Zhu Date: Sun, 11 Feb 2024 16:56:13 -0800 Subject: [PATCH 19/57] add timeout to slient pip install test --- autogen/coding/embedded_ipython_code_executor.py | 2 +- test/coding/test_embedded_ipython_code_executor.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/autogen/coding/embedded_ipython_code_executor.py b/autogen/coding/embedded_ipython_code_executor.py index 01a640015dd..c85798f7503 100644 --- a/autogen/coding/embedded_ipython_code_executor.py +++ b/autogen/coding/embedded_ipython_code_executor.py @@ -14,7 +14,7 @@ from .base import CodeBlock, CodeExtractor, CodeResult from .markdown_code_extractor import MarkdownCodeExtractor -__all__ = ("EmbeddedIPythonCodeExecutor",) +__all__ = ("EmbeddedIPythonCodeExecutor", "IPythonCodeResult") class IPythonCodeResult(CodeResult): diff --git a/test/coding/test_embedded_ipython_code_executor.py b/test/coding/test_embedded_ipython_code_executor.py index d1669096000..3ecb318f959 100644 --- a/test/coding/test_embedded_ipython_code_executor.py +++ b/test/coding/test_embedded_ipython_code_executor.py @@ -93,7 +93,7 @@ def test_timeout() -> None: @pytest.mark.skipif(skip, reason=skip_reason) def test_silent_pip_install() -> None: - executor = EmbeddedIPythonCodeExecutor() + executor = EmbeddedIPythonCodeExecutor(timeout=600) code_blocks = [CodeBlock(code="!pip install matplotlib numpy", language="python")] code_result = executor.execute_code_blocks(code_blocks) assert code_result.exit_code == 0 and code_result.output.strip() == "" From c48e26c117c7fd4cbaef275e271dd25217aba086 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sun, 11 Feb 2024 20:06:55 -0500 Subject: [PATCH 20/57] consolidate_chat_info --- autogen/agentchat/conversable_agent.py | 24 +++++-- test/agentchat/test_chats.py | 97 +++++++++++++++++++++++++- test/agentchat/test_function_call.py | 30 +++++++- 3 files changed, 143 insertions(+), 8 deletions(-) diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index a528104024b..f4e51078b7a 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -794,6 +794,9 @@ def my_summary_method( Returns: ChatResult: an ChatResult object. """ + _chat_info = context.copy() + _chat_info["recipient"] = recipient + self._consolidate_chat_info(_chat_info) for agent in [self, recipient]: agent._raise_exception_on_async_reply_functions() agent.previous_cache = agent.client_cache @@ -903,10 +906,6 @@ def my_summary_method( warnings.warn(f"Cannot extract summary using last_msg: {e}", UserWarning) elif isinstance(summary_method, Callable): summary = summary_method(recipient, self) - else: - warnings.warn( - f"Unsupported summary method: {summary_method}. Returning an empty string as a summary.", UserWarning - ) return summary def _reflection_with_llm( @@ -937,6 +936,22 @@ def _reflection_with_llm( response = self._generate_oai_reply_from_client(llm_client=llm_client, messages=messages, cache=cache) return response + def _consolidate_chat_info(self, chat_info: Union[Dict, List[Dict]]): + if isinstance(chat_info, dict): + chat_info = [chat_info] + for c in chat_info: + assert "recipient" in c, "recipient must be provided." + summary_method = c.get("summary_method") + assert ( + summary_method is None + or isinstance(summary_method, Callable) + or summary_method in ("last_msg", "reflection_with_llm") + ), "summary_method must be a string chosen from 'reflection_with_llm' or 'last_msg' or a callable, or None." + if summary_method == "reflection_with_llm": + assert ( + self.client is not None or c["recipient"].client is not None + ), "llm client must be set in either the recipient or sender when summary_method is reflection_with_llm." + def initiate_chats(self, chat_queue: List[Dict[str, Any]]) -> Dict[Agent, ChatResult]: """(Experimental) Initiate chats with multiple agents. TODO: add async version of this method. @@ -971,6 +986,7 @@ def my_summary_method( Returns: a dictionary of ChatResult object from the finished chats of particular agents. """ + self._consolidate_chat_info(chat_queue) receipts_set = set() for chat_info in chat_queue: assert "recipient" in chat_info, "recipient must be provided." diff --git a/test/agentchat/test_chats.py b/test/agentchat/test_chats.py index 45dafedabb6..e98c7921323 100644 --- a/test/agentchat/test_chats.py +++ b/test/agentchat/test_chats.py @@ -231,6 +231,98 @@ def my_summary_method(recipient, sender): # print(blogpost.summary, insights_and_blogpost) +@pytest.mark.skipif(skip_openai, reason="requested to skip openai tests") +def test_chats_exceptions(): + config_list = autogen.config_list_from_json( + OAI_CONFIG_LIST, + file_location=KEY_LOC, + ) + + financial_tasks = [ + """What are the full names of NVDA and TESLA.""", + """Get their stock price.""", + """Analyze pros and cons. Keep it short.""", + ] + + financial_assistant_1 = AssistantAgent( + name="Financial_assistant_1", + llm_config={"config_list": config_list}, + ) + financial_assistant_2 = AssistantAgent( + name="Financial_assistant_2", + llm_config={"config_list": config_list}, + ) + user = UserProxyAgent( + name="User", + human_input_mode="NEVER", + is_termination_msg=lambda x: x.get("content", "").find("TERMINATE") >= 0, + code_execution_config={ + "last_n_messages": 1, + "work_dir": "tasks", + "use_docker": False, + }, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly. + ) + + user_2 = UserProxyAgent( + name="User", + human_input_mode="NEVER", + is_termination_msg=lambda x: x.get("content", "").find("TERMINATE") >= 0, + code_execution_config={ + "last_n_messages": 1, + "work_dir": "tasks", + "use_docker": False, + }, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly. + ) + + try: + user.initiate_chats( + [ + { + "recipient": financial_assistant_1, + "message": financial_tasks[0], + "silent": False, + "summary_method": "last_msg", + }, + { + "recipient": financial_assistant_2, + "message": financial_tasks[2], + "summary_method": "llm", + "clear_history": False, + }, + ] + ) + except AssertionError as e: + print(e) + assert ( + str(e) + == "summary_method must be a string chosen from 'reflection_with_llm' or 'last_msg' or a callable, or None." + ) + + try: + user.initiate_chats( + [ + { + "recipient": financial_assistant_1, + "message": financial_tasks[0], + "silent": False, + "summary_method": "last_msg", + }, + { + "recipient": user_2, + "message": financial_tasks[2], + "clear_history": False, + "summary_method": "reflection_with_llm", + }, + ] + ) + except AssertionError as e: + print(e) + assert ( + str(e) + == "llm client must be set in either the recipient or sender when summary_method is reflection_with_llm." + ) + + @pytest.mark.skipif(skip_openai, reason="requested to skip openai tests") def test_chats_w_func(): config_list = autogen.config_list_from_json( @@ -293,7 +385,8 @@ def currency_calculator( if __name__ == "__main__": - test_chats() - test_chats_group() + # test_chats() + test_chats_exceptions() + # test_chats_group() # test_chats_w_func() # test_chat_messages_for_summary() diff --git a/test/agentchat/test_function_call.py b/test/agentchat/test_function_call.py index 52430c70bcb..75998e5ef98 100644 --- a/test/agentchat/test_function_call.py +++ b/test/agentchat/test_function_call.py @@ -256,10 +256,36 @@ def test_update_function(): assert "greet_user" not in messages2 print("Chat summary and cost", res2.summary, res2.cost) + try: + user_proxy.initiate_chat( + assistant, + message="What functions do you know about in the context of this conversation? End your response with 'TERMINATE'.", + summary_method="llm", + ) + except AssertionError as e: + print(e) + assert ( + str(e) + == "summary_method must be a string chosen from 'reflection_with_llm' or 'last_msg' or a callable, or None." + ) + + try: + user_proxy.initiate_chat( + recipient=user_proxy, + message="What functions do you know about in the context of this conversation? End your response with 'TERMINATE'.", + summary_method="reflection_with_llm", + ) + except AssertionError as e: + print(e) + assert ( + str(e) + == "llm client must be set in either the recipient or sender when summary_method is reflection_with_llm." + ) + if __name__ == "__main__": # test_json_extraction() # test_execute_function() test_update_function() - asyncio.run(test_a_execute_function()) - test_eval_math_responses() + # asyncio.run(test_a_execute_function()) + # test_eval_math_responses() From 48db5288cc174a7690f6ba63f3a3a702d34fa5dc Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sun, 11 Feb 2024 20:09:28 -0500 Subject: [PATCH 21/57] a_initiate_chat --- autogen/agentchat/conversable_agent.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index f4e51078b7a..895eb7b5122 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -839,6 +839,9 @@ async def a_initiate_chat( Returns: ChatResult: an ChatResult object. """ + _chat_info = context.copy() + _chat_info["recipient"] = recipient + self._consolidate_chat_info(_chat_info) self._prepare_chat(recipient, clear_history) for agent in [self, recipient]: agent.previous_cache = agent.client_cache From f6bcb9f4877035f19ebcb7a50772787f20ee47f1 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sun, 11 Feb 2024 20:46:01 -0500 Subject: [PATCH 22/57] AssertionError tests --- test/agentchat/test_chats.py | 23 ++++++++--------------- test/agentchat/test_function_call.py | 22 ++++++++-------------- 2 files changed, 16 insertions(+), 29 deletions(-) diff --git a/test/agentchat/test_chats.py b/test/agentchat/test_chats.py index e98c7921323..66c2939efc6 100644 --- a/test/agentchat/test_chats.py +++ b/test/agentchat/test_chats.py @@ -274,7 +274,10 @@ def test_chats_exceptions(): }, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly. ) - try: + with pytest.raises( + AssertionError, + match="summary_method must be a string chosen from 'reflection_with_llm' or 'last_msg' or a callable, or None.", + ): user.initiate_chats( [ { @@ -291,14 +294,10 @@ def test_chats_exceptions(): }, ] ) - except AssertionError as e: - print(e) - assert ( - str(e) - == "summary_method must be a string chosen from 'reflection_with_llm' or 'last_msg' or a callable, or None." - ) - - try: + with pytest.raises( + AssertionError, + match="llm client must be set in either the recipient or sender when summary_method is reflection_with_llm.", + ): user.initiate_chats( [ { @@ -315,12 +314,6 @@ def test_chats_exceptions(): }, ] ) - except AssertionError as e: - print(e) - assert ( - str(e) - == "llm client must be set in either the recipient or sender when summary_method is reflection_with_llm." - ) @pytest.mark.skipif(skip_openai, reason="requested to skip openai tests") diff --git a/test/agentchat/test_function_call.py b/test/agentchat/test_function_call.py index 75998e5ef98..09d17db5eb9 100644 --- a/test/agentchat/test_function_call.py +++ b/test/agentchat/test_function_call.py @@ -256,31 +256,25 @@ def test_update_function(): assert "greet_user" not in messages2 print("Chat summary and cost", res2.summary, res2.cost) - try: + with pytest.raises( + AssertionError, + match="summary_method must be a string chosen from 'reflection_with_llm' or 'last_msg' or a callable, or None.", + ): user_proxy.initiate_chat( assistant, message="What functions do you know about in the context of this conversation? End your response with 'TERMINATE'.", summary_method="llm", ) - except AssertionError as e: - print(e) - assert ( - str(e) - == "summary_method must be a string chosen from 'reflection_with_llm' or 'last_msg' or a callable, or None." - ) - try: + with pytest.raises( + AssertionError, + match="llm client must be set in either the recipient or sender when summary_method is reflection_with_llm.", + ): user_proxy.initiate_chat( recipient=user_proxy, message="What functions do you know about in the context of this conversation? End your response with 'TERMINATE'.", summary_method="reflection_with_llm", ) - except AssertionError as e: - print(e) - assert ( - str(e) - == "llm client must be set in either the recipient or sender when summary_method is reflection_with_llm." - ) if __name__ == "__main__": From d9739e907147f24570ec530e2f3203d092b0ae1a Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sun, 11 Feb 2024 20:48:19 -0500 Subject: [PATCH 23/57] AssertionError test --- test/agentchat/test_chats.py | 22 ++++++++-------------- test/agentchat/test_function_call.py | 22 ++++++++-------------- 2 files changed, 16 insertions(+), 28 deletions(-) diff --git a/test/agentchat/test_chats.py b/test/agentchat/test_chats.py index e98c7921323..a1771fe7c2e 100644 --- a/test/agentchat/test_chats.py +++ b/test/agentchat/test_chats.py @@ -274,7 +274,10 @@ def test_chats_exceptions(): }, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly. ) - try: + with pytest.raises( + AssertionError, + match="summary_method must be a string chosen from 'reflection_with_llm' or 'last_msg' or a callable, or None.", + ): user.initiate_chats( [ { @@ -291,14 +294,11 @@ def test_chats_exceptions(): }, ] ) - except AssertionError as e: - print(e) - assert ( - str(e) - == "summary_method must be a string chosen from 'reflection_with_llm' or 'last_msg' or a callable, or None." - ) - try: + with pytest.raises( + AssertionError, + match="llm client must be set in either the recipient or sender when summary_method is reflection_with_llm.", + ): user.initiate_chats( [ { @@ -315,12 +315,6 @@ def test_chats_exceptions(): }, ] ) - except AssertionError as e: - print(e) - assert ( - str(e) - == "llm client must be set in either the recipient or sender when summary_method is reflection_with_llm." - ) @pytest.mark.skipif(skip_openai, reason="requested to skip openai tests") diff --git a/test/agentchat/test_function_call.py b/test/agentchat/test_function_call.py index 75998e5ef98..09d17db5eb9 100644 --- a/test/agentchat/test_function_call.py +++ b/test/agentchat/test_function_call.py @@ -256,31 +256,25 @@ def test_update_function(): assert "greet_user" not in messages2 print("Chat summary and cost", res2.summary, res2.cost) - try: + with pytest.raises( + AssertionError, + match="summary_method must be a string chosen from 'reflection_with_llm' or 'last_msg' or a callable, or None.", + ): user_proxy.initiate_chat( assistant, message="What functions do you know about in the context of this conversation? End your response with 'TERMINATE'.", summary_method="llm", ) - except AssertionError as e: - print(e) - assert ( - str(e) - == "summary_method must be a string chosen from 'reflection_with_llm' or 'last_msg' or a callable, or None." - ) - try: + with pytest.raises( + AssertionError, + match="llm client must be set in either the recipient or sender when summary_method is reflection_with_llm.", + ): user_proxy.initiate_chat( recipient=user_proxy, message="What functions do you know about in the context of this conversation? End your response with 'TERMINATE'.", summary_method="reflection_with_llm", ) - except AssertionError as e: - print(e) - assert ( - str(e) - == "llm client must be set in either the recipient or sender when summary_method is reflection_with_llm." - ) if __name__ == "__main__": From dfaa118a61fe0562407f781cb546eef59dd5916b Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sun, 11 Feb 2024 20:51:44 -0500 Subject: [PATCH 24/57] update tests --- test/agentchat/test_chats.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/agentchat/test_chats.py b/test/agentchat/test_chats.py index a1771fe7c2e..dfa1617cba5 100644 --- a/test/agentchat/test_chats.py +++ b/test/agentchat/test_chats.py @@ -222,7 +222,6 @@ def my_summary_method(recipient, sender): writer_res = user.get_chat_results(writer) all_res = user.get_chat_results() - assert all_res[financial_assistant_1].summary == "What are the full names of NVDA and TESLA." print(writer_res.summary, writer_res.cost) print(all_res[financial_assistant_1].human_input) print(all_res[financial_assistant_1].summary) @@ -379,8 +378,8 @@ def currency_calculator( if __name__ == "__main__": - # test_chats() - test_chats_exceptions() + test_chats() + # test_chats_exceptions() # test_chats_group() # test_chats_w_func() # test_chat_messages_for_summary() From 11e2959ca038c23d22669145701cf94648d38e64 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sun, 11 Feb 2024 20:56:07 -0500 Subject: [PATCH 25/57] update test --- test/agentchat/test_nested.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/test/agentchat/test_nested.py b/test/agentchat/test_nested.py index 9fa4ef78dfd..f826a1eeb19 100644 --- a/test/agentchat/test_nested.py +++ b/test/agentchat/test_nested.py @@ -155,7 +155,6 @@ def reply_num(self): ), # llm_config={"temperature":1, "cache_seed": 1, "config_list": config_list_gpt35}, llm_config={"config_list": config_list_gpt4}, - # llm_config={"temperature":1, "cache_seed": 1, "config_list": config_list_gpt4}, max_consecutive_auto_reply=max_turn, ) @@ -183,19 +182,14 @@ def board_chat_func(chat_queue, recipient, messages, sender, config): useful_msg = messages[-1].copy() useful_msg["content"] = useful_msg.get("content", "") + f"The current board is:\n {board} ." oai_messages = [messages[-1]] - # _, message = recipient.generate_oai_reply(messages + board_state_msg, sender) _, message = recipient.generate_oai_reply(oai_messages + board_state_msg, sender) c["message"] = message - # c["carryover"] = f"Current board:\n{board}" # NOTE: in the old code, this is added as system message chat_queue[0] = c - # print("nessss") res = recipient.initiate_chats(chat_queue) last_res = list(res.values())[-1] - # return True, message + "My Move is:\n" + last_res.summary return True, last_res.summary def board_reply(recipient, messages, sender, config): - # print("resplyssss") if recipient.reply_num >= max_turn: return True, None org_msg = messages[-1].copy() From da7980bddd20e521568b8bf932afbc4bb17c7f92 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sun, 11 Feb 2024 21:46:15 -0500 Subject: [PATCH 26/57] remove redudant file --- test/ollama.sh | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 test/ollama.sh diff --git a/test/ollama.sh b/test/ollama.sh deleted file mode 100644 index 754d745c8a8..00000000000 --- a/test/ollama.sh +++ /dev/null @@ -1,15 +0,0 @@ -curl http://localhost:11434/v1/chat/completions \ - -H "Content-Type: application/json" \ - -d '{ - "model": codellama", - "messages": [ - { - "role": "system", - "content": "You are a helpful assistant." - }, - { - "role": "user", - "content": "Hello!" - } - ] - } From edf3926ffaef37b7fa3333a703c7a5f513f46e6b Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sun, 11 Feb 2024 23:19:56 -0500 Subject: [PATCH 27/57] kwargs --- autogen/agentchat/conversable_agent.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index 31e3b0ba261..ff461e0d119 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -331,10 +331,7 @@ def register_nested_chats( chat_queue, chat_reply_func="auto", position: int = 2, - config: Optional[Any] = None, - reset_config: Optional[Callable] = None, - *, - ignore_async_in_sync_chat: bool = False, + **kwargs, ): """Register a nested chat reply function. Args: @@ -353,9 +350,7 @@ def chat_reply_func( ) -> Tuple[bool, Union[str, Dict, None]]: ``` position (int): Ref to `register_reply` for details. Default to 2. It means we first check the termination and human reply, then check the registered nested chat reply. - config: Ref to `register_reply` for details. - reset_config: Ref to `register_reply` for details. - ignore_async_in_sync_chat: Ref to `register_reply` for details. + kwargs: Ref to `register_reply` for details. """ from functools import partial @@ -365,7 +360,12 @@ def chat_reply_func( reply_func = partial(chat_reply_func, chat_queue) self.register_reply( - trigger, reply_func, position, config, reset_config, ignore_async_in_sync_chat=ignore_async_in_sync_chat + trigger, + reply_func, + position, + kwargs.get("config"), + kwargs.get("reset_config"), + ignore_async_in_sync_chat=kwargs.get("ignore_async_in_sync_chat"), ) # self._exclude_reply_list.append(reply_func) From 1c7cac75d4e6bae324cb7f5580e5570ec86a8fb3 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Wed, 14 Feb 2024 19:11:19 -0500 Subject: [PATCH 28/57] update notebook --- notebook/agentchat_nestedchat_chess.ipynb | 938 ++++++++-------------- 1 file changed, 319 insertions(+), 619 deletions(-) diff --git a/notebook/agentchat_nestedchat_chess.ipynb b/notebook/agentchat_nestedchat_chess.ipynb index 7cb0ebca509..98c4d648c3e 100644 --- a/notebook/agentchat_nestedchat_chess.ipynb +++ b/notebook/agentchat_nestedchat_chess.ipynb @@ -24,7 +24,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -38,7 +38,7 @@ "\n", "import autogen\n", "\n", - "%pip install chess -U" + "# %%pip install chess -U" ] }, { @@ -51,7 +51,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -59,7 +59,7 @@ " \"OAI_CONFIG_LIST\",\n", ")\n", "\n", - "max_turn = 50\n", + "max_turn = 10\n", "\n", "sys_msg_tmpl = \"\"\"Your name is {name} and you are a chess player.\n", "You are playing against {opponent_name}.\n", @@ -88,7 +88,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -138,292 +138,151 @@ ")" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Register reply func and nested chats" - ] - }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ + "chat_infochat_info {'sender': , 'recipient': , 'message': 'Your turn.', 'carryover': []}\n", "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", "\n", "Your turn.\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[34mStart a new chat with the following message: \n", - "Alright, let's kick this party off with something classic. e7-e5, control the center, my friend!\n", - "\n", - "With the following carryover: \n", - "\u001b[0m\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", + "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"Alright, let's start this dance with a classic move.\\n\\ne7-e5\", 'goal': 'Your turn.', 'carryover': []}\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "Alright, let's kick this party off with something classic. e7-e5, control the center, my friend!\n", + "Alright, let's start this dance with a classic move.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", - "\n", - "Error: illegal uci: 'e7e5' in rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\n", + "e7-e5\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", - "\n", - "Ah, my apologies, it's your move to set the stage. Take it away, PlayerWhite!\n", - "\n", - "--------------------------------------------------------------------------------\n" - ] - }, - { - "data": { - "image/svg+xml": [ - "
r n b q k b n r\n",
-       "p p p p p p p p\n",
-       ". . . . . . . .\n",
-       ". . . . . . . .\n",
-       ". . . . P . . .\n",
-       ". . . . . . . .\n",
-       "P P P P . P P P\n",
-       "R N B Q K B N R
" - ], - "text/plain": [ - "'
r n b q k b n r\\np p p p p p p p\\n. . . . . . . .\\n. . . . . . . .\\n. . . . P . . .\\n. . . . . . . .\\nP P P P . P P P\\nR N B Q K B N R
'" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", "\n", - "Ah, my apologies, it's your move to set the stage. Take it away, PlayerWhite!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", - "\n", - "Ah, my apologies, it's your move to set the stage. Take it away, PlayerWhite!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[34mStart a new chat with the following message: \n", - "Let's start with the classic King's Pawn opening, shall we? e2e4\n", - "It's always nice to see the center of the board opening up, inviting a dance of pieces! Your move, PlayerBlack.\n", - "\n", - "With the following carryover: \n", - "\u001b[0m\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", - "\n", - "Let's start with the classic King's Pawn opening, shall we? e2e4\n", - "It's always nice to see the center of the board opening up, inviting a dance of pieces! Your move, PlayerBlack.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", - "\n", - "Error: illegal uci: 'e2e4' in rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", - "\n", - "e4\n", - "The first pawn moves forward, opening up lines for queen and bishop. It's always fascinating to see what kind of battle will unfold on the board. Your move!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", - "\n", - "Error: illegal uci: 'e2e4' in rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", - "\n", - "I see there's been a misunderstanding about the board's starting position. The move e2 to e4 is indeed correct and the board has been updated to reflect that move. Now it's your turn, PlayerBlack. Please proceed with your move.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", - "\n", - "Error: illegal uci: 'e2e4' in rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", - "\n", - "It seems there was some confusion, but now the board is accurately displaying the position after my first move, e4. The board is ready for your move, PlayerBlack. Go ahead!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "Error: illegal uci: 'e7e5' in rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1The current board is r n b q k b n r\n", + "p p p p p p p p\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + "P P P P P P P P\n", + "R N B Q K B N R\n", "\n", - "Error: illegal uci: 'e2e4' in rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1\n", + "Try again to respond to the following message as if nothing happened.Your turn.\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", - "\n", - "Thank you for your patience. It seems we've successfully identified that the move e2 to e4 has been played. The board is set correctly for black to make a move.\n", - "\n", - "PlayerBlack, your turn. Let's see what opening you have in store for us!\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "Oh, sorry about that hiccup. Let's get our pieces moving.\n", "\n", - "Error: illegal uci: 'e2e4' in rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1\n", + "g8f6\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", - "\n", - "The board indicates that my first move, e4, has been played. It's your turn PlayerBlack. Please make your move.\n", - "\n", - "--------------------------------------------------------------------------------\n" - ] - }, - { - "data": { - "image/svg+xml": [ - "
r n b q k b n r\n",
-       "p p p p . p p p\n",
-       ". . . . . . . .\n",
-       ". . . . p . . .\n",
-       ". . . . P . . .\n",
-       ". . . . . . . .\n",
-       "P P P P . P P P\n",
-       "R N B Q K B N R
" - ], - "text/plain": [ - "'
r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . . . .\\nP P P P . P P P\\nR N B Q K B N R
'" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", - "\n", - "The board indicates that my first move, e4, has been played. It's your turn PlayerBlack. Please make your move.\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", + "Error: illegal uci: 'g8f6' in rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1The current board is r n b q k b n r\n", + "p p p p p p p p\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + "P P P P P P P P\n", + "R N B Q K B N R\n", "\n", - "The board indicates that my first move, e4, has been played. It's your turn PlayerBlack. Please make your move.\n", + "Try again to respond to the following message as if nothing happened.Your turn.\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[34mStart a new chat with the following message: \n", - "Ah, seems like you've started with the classic King's Pawn opening. Let's spice things up; I'll counter with the Sicilian Defense.\n", - "\n", - "e7e5\n", - "\n", - "With the following carryover: \n", - "\u001b[0m\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "Ah, seems like you've started with the classic King's Pawn opening. Let's spice things up; I'll counter with the Sicilian Defense.\n", + "Let's get this match underway.\n", "\n", "e7e5\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", "\n", - "Error: expected uci string to be of length 4 or 5: \"e7e5 does not correspond to the Sicilian Defense, which would be c7c5. However, you've mentioned e7e5 which is a counter move that leads to an Open Game or Double King's Pawn opening. Assuming you're asking to execute the move you've mentioned:\\n\\ne7e5\"\n", + "Error: illegal uci: 'e7e5' in rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1The current board is r n b q k b n r\n", + "p p p p p p p p\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + "P P P P P P P P\n", + "R N B Q K B N R\n", + "\n", + "Try again to respond to the following message as if nothing happened.Your turn.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "My apologies for the mix-up. Let's indeed follow the path of the Double King's Pawn opening.\n", + "Ah, I see now. Well, if it's my turn, let's kick this off with a classic move.\n", "\n", "e7e5\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", "\n", - "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", - "\n", - "Oops, looks like a little slip of the hand there,—I'm getting ahead of myself!\n", - "\n", - "I'll play c7c5, officially inviting you into the exciting territory of the Sicilian Defense.\n", - "\n", - "c7c5\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "Error: illegal uci: 'e7e5' in rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1The current board is r n b q k b n r\n", + "p p p p p p p p\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + "P P P P P P P P\n", + "R N B Q K B N R\n", "\n", - "Error: illegal uci: 'c7c5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", + "Try again to respond to the following message as if nothing happened.Your turn.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "I see that we need to clarify the position. My mistake - you've played e4 and I'll respond accordingly. Now for the Sicilian Defense:\n", + "My apologies for the confusion earlier. Now, let's start things properly.\n", "\n", - "c7c5\n", + "1... e7e5\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", "\n", - "Error: illegal uci: 'c7c5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", - "\n", - "My apologies for the confusion earlier. The board is set after 1.e4 e5. It's my move as Black.\n", - "\n", - "Alright, let's go with the Knight to the classical f6 square.\n", - "\n", - "g8f6\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "Error: illegal uci: 'e7e5' in rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1The current board is r n b q k b n r\n", + "p p p p p p p p\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + "P P P P P P P P\n", + "R N B Q K B N R\n", "\n", - "Error: illegal uci: 'g8f6' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", + "Try again to respond to the following message as if nothing happened.Your turn.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "It appears to be White's move. Please proceed with your next move, PlayerWhite. Once you've made your move, I'll be happy to continue the dance of the pieces.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", - "\n", - "Error: expected uci string to be of length 4 or 5: 'Your message does not indicate a specific move to make on the chessboard. Please provide a move in the format of the starting square to the ending square, such as \"e2e4\" for a pawn move from e2 to e4.'\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "My apologies for the oversight, let's get the game on track.\n", "\n", - "Ah, it appears we had a small oversight with whose turn it was. Since White moved pawn to e4 and Black followed with pawn to e5, it's indeed White's turn. Over to you, PlayerWhite.\n", + "d7d5\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", "\n", - "Error: expected uci string to be of length 4 or 5: \"It's your turn to provide a move for White. Please tell me which move you'd like to make.\"\n", + "Error: illegal uci: 'd7d5' in rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1The current board is r n b q k b n r\n", + "p p p p p p p p\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + "P P P P P P P P\n", + "R N B Q K B N R\n", + "\n", + "Try again to respond to the following message as if nothing happened.Your turn.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "Ah, I see. Then let's continue this classic ballet of chess with a typical response:\n", - "\n", - "ng1f3\n", - "\n", - "Your move!\n", + "Ah, my bad! I realise what's going on now - it's White's move first! PlayerWhite, go ahead, it's your turn.\n", "\n", "--------------------------------------------------------------------------------\n" ] @@ -432,16 +291,16 @@ "data": { "image/svg+xml": [ "
r n b q k b n r\n",
-       "p p p p . p p p\n",
+       "p p p p p p p p\n",
+       ". . . . . . . .\n",
        ". . . . . . . .\n",
-       ". . . . p . . .\n",
        ". . . . P . . .\n",
-       ". . . . . N . .\n",
+       ". . . . . . . .\n",
        "P P P P . P P P\n",
-       "R N B Q K B . R
" + "R N B Q K B N R" ], "text/plain": [ - "'
r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . N . .\\nP P P P . P P P\\nR N B Q K B . R
'" + "'
r n b q k b n r\\np p p p p p p p\\n. . . . . . . .\\n. . . . . . . .\\n. . . . P . . .\\n. . . . . . . .\\nP P P P . P P P\\nR N B Q K B N R
'" ] }, "metadata": {}, @@ -453,578 +312,408 @@ "text": [ "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", "\n", - "Ah, I see. Then let's continue this classic ballet of chess with a typical response:\n", - "\n", - "ng1f3\n", - "\n", - "Your move!\n", + "Ah, my bad! I realise what's going on now - it's White's move first! PlayerWhite, go ahead, it's your turn.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", "\n", - "Ah, I see. Then let's continue this classic ballet of chess with a typical response:\n", - "\n", - "ng1f3\n", - "\n", - "Your move!\n", + "Ah, my bad! I realise what's going on now - it's White's move first! PlayerWhite, go ahead, it's your turn.\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[34mStart a new chat with the following message: \n", - "d7d5\n", - "\n", - "All right, the dance continues as the pawns stride forward. Your move, PlayerWhite!\n", - "\n", - "With the following carryover: \n", - "\u001b[0m\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", + "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"No worries, PlayerBlack! Now, let's kick this off with classic King's Pawn opening.\\n\\ne2e4\", 'goal': \"Ah, my bad! I realise what's going on now - it's White's move first! PlayerWhite, go ahead, it's your turn.\", 'carryover': []}\n", "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", - "d7d5\n", - "\n", - "All right, the dance continues as the pawns stride forward. Your move, PlayerWhite!\n", - "\n", - "--------------------------------------------------------------------------------\n" - ] - }, - { - "data": { - "image/svg+xml": [ - "
r n b q k b n r\n",
-       "p p p . . p p p\n",
-       ". . . . . . . .\n",
-       ". . . p p . . .\n",
-       ". . . . P . . .\n",
-       ". . . . . N . .\n",
-       "P P P P . P P P\n",
-       "R N B Q K B . R
" - ], - "text/plain": [ - "'
r n b q k b n r\\np p p . . p p p\\n. . . . . . . .\\n. . . p p . . .\\n. . . . P . . .\\n. . . . . N . .\\nP P P P . P P P\\nR N B Q K B . R
'" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", - "\n", - "d7d5\n", + "No worries, PlayerBlack! Now, let's kick this off with classic King's Pawn opening.\n", "\n", - "All right, the dance continues as the pawns stride forward. Your move, PlayerWhite!\n", + "e2e4\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", "\n", - "d7d5\n", + "Error: illegal uci: 'e2e4' in rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1The current board is r n b q k b n r\n", + "p p p p p p p p\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + ". . . . P . . .\n", + ". . . . . . . .\n", + "P P P P . P P P\n", + "R N B Q K B N R\n", "\n", - "All right, the dance continues as the pawns stride forward. Your move, PlayerWhite!\n", + "Try again to respond to the following message as if nothing happened.Ah, my bad! I realise what's going on now - it's White's move first! PlayerWhite, go ahead, it's your turn.\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[34mStart a new chat with the following message: \n", - "c2c4\n", - "\n", - "Charging forward with the queen's gambit! The board's gauntlet has been thrown; do you accept the challenge or have another plan up your sleeve, PlayerBlack?\n", - "\n", - "With the following carryover: \n", - "\u001b[0m\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", - "\n", - "c2c4\n", - "\n", - "Charging forward with the queen's gambit! The board's gauntlet has been thrown; do you accept the challenge or have another plan up your sleeve, PlayerBlack?\n", - "\n", - "--------------------------------------------------------------------------------\n" - ] - }, - { - "data": { - "image/svg+xml": [ - "
r n b q k b n r\n",
-       "p p p . . p p p\n",
-       ". . . . . . . .\n",
-       ". . . p p . . .\n",
-       ". . P . P . . .\n",
-       ". . . . . N . .\n",
-       "P P . P . P P P\n",
-       "R N B Q K B . R
" - ], - "text/plain": [ - "'
r n b q k b n r\\np p p . . p p p\\n. . . . . . . .\\n. . . p p . . .\\n. . P . P . . .\\n. . . . . N . .\\nP P . P . P P P\\nR N B Q K B . R
'" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", - "c2c4\n", + "Haha, seems we had a little mix-up there, PlayerBlack. Alright, let's get down to the real business – opening move for real this time.\n", "\n", - "Charging forward with the queen's gambit! The board's gauntlet has been thrown; do you accept the challenge or have another plan up your sleeve, PlayerBlack?\n", + "e2e4\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", "\n", - "c2c4\n", + "Error: illegal uci: 'e2e4' in rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1The current board is r n b q k b n r\n", + "p p p p p p p p\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + ". . . . P . . .\n", + ". . . . . . . .\n", + "P P P P . P P P\n", + "R N B Q K B N R\n", "\n", - "Charging forward with the queen's gambit! The board's gauntlet has been thrown; do you accept the challenge or have another plan up your sleeve, PlayerBlack?\n", + "Try again to respond to the following message as if nothing happened.Ah, my bad! I realise what's going on now - it's White's move first! PlayerWhite, go ahead, it's your turn.\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[34mStart a new chat with the following message: \n", - "e7e6\n", - "\n", - "I see you're in the mood for a classic opening, PlayerWhite! Let's keep it civil with a move straight out of the textbook. Your turn!\n", - "\n", - "With the following carryover: \n", - "\u001b[0m\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", - "e7e6\n", + "May the chessboard be our canvas. Let's start with something creative. How about a knight's leap?\n", "\n", - "I see you're in the mood for a classic opening, PlayerWhite! Let's keep it civil with a move straight out of the textbook. Your turn!\n", + "g1f3\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", "\n", - "Error: illegal uci: 'e7e6' in rnbqkbnr/ppp2ppp/8/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R b KQkq - 0 3\n", + "Error: illegal uci: 'g1f3' in rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1The current board is r n b q k b n r\n", + "p p p p p p p p\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + ". . . . P . . .\n", + ". . . . . . . .\n", + "P P P P . P P P\n", + "R N B Q K B N R\n", + "\n", + "Try again to respond to the following message as if nothing happened.Ah, my bad! I realise what's going on now - it's White's move first! PlayerWhite, go ahead, it's your turn.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", - "My apologies, it seems there was a mistake in the move communication. It's your move, PlayerBlack. The board is all set for an interesting game ahead!\n", + "Ah, it seems I may have attempted to re-play my first move. My apologies for the oversight. It's your turn, PlayerBlack. The board is set for your response.\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", "\n", - "Error: expected uci string to be of length 4 or 5: \"I need a specific move in standard chess notation (e.g., e2e4, g8f6, etc.) to convert it into a UCI move. Please provide me with the move you'd like to make.\"\n", + "Ah, it seems I may have attempted to re-play my first move. My apologies for the oversight. It's your turn, PlayerBlack. The board is set for your response.\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", - "\n", - "e2e3\n", + "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': 'No problem at all, PlayerWhite, it happens to the best of us! Let\\'s have a good game, shall we?\\n\\nI\\'ll make my move now. I\\'ll go for the classic and solid \"e7 to e5\".\\n\\ne7e5', 'goal': \"Ah, it seems I may have attempted to re-play my first move. My apologies for the oversight. It's your turn, PlayerBlack. The board is set for your response.\", 'carryover': []}\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "Just keeping things flexible, PlayerBlack! It's your move – let's see where this symphony of pawns and pieces takes us.\n", + "No problem at all, PlayerWhite, it happens to the best of us! Let's have a good game, shall we?\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "I'll make my move now. I'll go for the classic and solid \"e7 to e5\".\n", "\n", - "Error: illegal uci: 'e2e3' in rnbqkbnr/ppp2ppp/8/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R b KQkq - 0 3\n", + "e7e5\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", "\n", - "My mistake, PlayerBlack. The board is set, and it is indeed your move. Please proceed with your next chess move.\n", + "No problem at all, PlayerWhite, it happens to the best of us! Let's have a good game, shall we?\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "I'll make my move now. I'll go for the classic and solid \"e7 to e5\".\n", "\n", - "Error: expected uci string to be of length 4 or 5: 'I am sorry, but you did not provide a specific move to translate into UCI format. Please tell me the move you would like to make in terms of the starting and ending squares, such as \"e2 to e4\" or \"knight from b1 to c3\".'\n", + "e7e5\n", "\n", "--------------------------------------------------------------------------------\n", + "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"Ah, the classic King's Pawn Opening. A great choice to start things off! I'll go with a knight's leap, always aiming to control the center.\\n\\ng1f3\\n\\nLet the game advance in the spirit of good sportsmanship!\", 'goal': 'No problem at all, PlayerWhite, it happens to the best of us! Let\\'s have a good game, shall we?\\n\\nI\\'ll make my move now. I\\'ll go for the classic and solid \"e7 to e5\".\\n\\ne7e5', 'carryover': []}\n", "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", - "It looks like it's your move, PlayerBlack. The board is all set and the game is ongoing. Please provide your next move.\n", + "Ah, the classic King's Pawn Opening. A great choice to start things off! I'll go with a knight's leap, always aiming to control the center.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "g1f3\n", "\n", - "Error: expected uci string to be of length 4 or 5: 'To provide you with a UCI move, I need to know the actual move you wish to make. Please tell me the piece you\\'d like to move and its destination square, such as \"e7 to e5\".'\n", + "Let the game advance in the spirit of good sportsmanship!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", "\n", - "It's your turn, PlayerBlack. Please proceed with your move using the standard chess notation. For example, if you want to move a pawn from d7 to d6, you would say \"d7d6\". Looking forward to seeing your strategy unfold!\n", + "Ah, the classic King's Pawn Opening. A great choice to start things off! I'll go with a knight's leap, always aiming to control the center.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "g1f3\n", "\n", - "Error: expected uci string to be of length 4 or 5: 'Your message does not contain a move for me to translate. Please provide a specific move in standard chess notation.'\n", + "Let the game advance in the spirit of good sportsmanship!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"It's always a pleasure to see classical play! I'll meet you in the center with my pawn.\\n\\ne7e5\\n\\nMay our battle be as enthralling as the great matches of the past!\", 'goal': \"Ah, the classic King's Pawn Opening. A great choice to start things off! I'll go with a knight's leap, always aiming to control the center.\\n\\ng1f3\\n\\nLet the game advance in the spirit of good sportsmanship!\", 'carryover': []}\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "Oh, it's your move, PlayerBlack. The board is ready for your next play. Please make your next move in standard chess notation (e.g., e7e5, g8f6, etc.).\n", + "It's always a pleasure to see classical play! I'll meet you in the center with my pawn.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "e7e5\n", "\n", - "Error: expected uci string to be of length 4 or 5: 'You have not provided a specific move, just examples of standard chess notation. To generate a UCI move, I need you to tell me the exact move you want to make, like \"pawn to e5\" or \"knight to f6\". Please provide a specific move.'\n", + "May our battle be as enthralling as the great matches of the past!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", "\n", - "It's still your move, PlayerBlack. Looking at the board, it's clear you have several options to develop your pieces. When you're ready, please provide your move.\n", + "It's always a pleasure to see classical play! I'll meet you in the center with my pawn.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "e7e5\n", "\n", - "Error: expected uci string to be of length 4 or 5: \"I'm sorry, but as an AI, I need specific details on the current state of the game or a specific move communicated to me in order to generate a UCI move string. Could you please provide the move you would like to make?\"\n", + "May our battle be as enthralling as the great matches of the past!\n", "\n", "--------------------------------------------------------------------------------\n", + "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"Indeed, the central tension is the essence of the game! Let's get our pieces ready for the upcoming symphony of moves.\\n\\ne2e4\\n\\nTalking about great matches, which classic chess game comes to your mind first?\", 'goal': \"It's always a pleasure to see classical play! I'll meet you in the center with my pawn.\\n\\ne7e5\\n\\nMay our battle be as enthralling as the great matches of the past!\", 'carryover': []}\n", "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", - "It seems I made an error previously in parsing the game state. PlayerBlack, it appears to be your move, with the position as follows:\n", + "Indeed, the central tension is the essence of the game! Let's get our pieces ready for the upcoming symphony of moves.\n", "\n", - "```\n", - "r n b q k b n r\n", - "p p p . . p p p\n", - ". . . . . . . .\n", - ". . . p p . . .\n", - ". . P . P . . .\n", - ". . . . . N . .\n", - "P P . P . P P P\n", - "R N B Q K B . R\n", - "```\n", + "e2e4\n", "\n", - "Now that the board state is clear, please make your next move using standard algebraic notation (e.g., e7e5, Ng8f6, etc.), and I'll communicate it forward in UCI format.\n", + "Talking about great matches, which classic chess game comes to your mind first?\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", - "\n", - "Error: illegal uci: 'e7e5' in rnbqkbnr/ppp2ppp/8/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R b KQkq - 0 3\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", "\n", - "Indeed, the move e7e5 is illegal given the current position since there is a pawn on d5. PlayerBlack, please make a legal move considering the current board state.\n", + "Indeed, the central tension is the essence of the game! Let's get our pieces ready for the upcoming symphony of moves.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "e2e4\n", "\n", - "Error: illegal uci: 'd7d5' in rnbqkbnr/ppp2ppp/8/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R b KQkq - 0 3\n", + "Talking about great matches, which classic chess game comes to your mind first?\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': 'e7e5\\n\\nOh, for classical games, it has to be the \"Evergreen Game\" between Adolf Anderssen and Jean Dufresne. The way Anderssen conducted a dazzling combination to win has always been an inspiration. How about you? What\\'s your favorite historical masterpiece on the 64 squares?', 'goal': \"Indeed, the central tension is the essence of the game! Let's get our pieces ready for the upcoming symphony of moves.\\n\\ne2e4\\n\\nTalking about great matches, which classic chess game comes to your mind first?\", 'carryover': []}\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "It appears there's some confusion with the moves. Let me clarify the board position for both of us. As of now, there's a pawn on d5, so it cannot be moved to d5 again.\n", + "e7e5\n", "\n", - "PlayerBlack, considering the position, it looks like your pawns on e6 and c7 are free to move, or perhaps you could develop one of your knights. Please provide a legal move.\n", + "Oh, for classical games, it has to be the \"Evergreen Game\" between Adolf Anderssen and Jean Dufresne. The way Anderssen conducted a dazzling combination to win has always been an inspiration. How about you? What's your favorite historical masterpiece on the 64 squares?\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", + "\n", + "e7e5\n", "\n", - "Error: illegal uci: 'e7e6' in rnbqkbnr/ppp2ppp/8/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R b KQkq - 0 3\n", + "Oh, for classical games, it has to be the \"Evergreen Game\" between Adolf Anderssen and Jean Dufresne. The way Anderssen conducted a dazzling combination to win has always been an inspiration. How about you? What's your favorite historical masterpiece on the 64 squares?\n", "\n", "--------------------------------------------------------------------------------\n", + "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': 'e2e4\\n\\nAh, the Evergreen Game is certainly a classic! I also have a soft spot for the \"Immortal Game\" played by Adolf Anderssen and Lionel Kieseritzky in 1851. Anderssen\\'s bold sacrifices opened the way to a king hunt that is still talked about today. It\\'s always fascinating to witness sheer creativity on the board. Now, let\\'s see how our own game unfolds!', 'goal': 'e7e5\\n\\nOh, for classical games, it has to be the \"Evergreen Game\" between Adolf Anderssen and Jean Dufresne. The way Anderssen conducted a dazzling combination to win has always been an inspiration. How about you? What\\'s your favorite historical masterpiece on the 64 squares?', 'carryover': []}\n", "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", - "Oh, looks like we had a bit of miscommunication. You can't move the e7 pawn to e6 since you already have a pawn there. The board indicates that your previous move was d7 to d5. Perhaps you might consider developing your knights or your other pieces. Please provide a different legal move, PlayerBlack.\n", + "e2e4\n", "\n", - "--------------------------------------------------------------------------------\n" - ] - }, - { - "data": { - "image/svg+xml": [ - "
r n b q k b . r\n",
-       "p p p . . p p p\n",
-       ". . . . . n . .\n",
-       ". . . p p . . .\n",
-       ". . P . P . . .\n",
-       ". . . . . N . .\n",
-       "P P . P . P P P\n",
-       "R N B Q K B . R
" - ], - "text/plain": [ - "'
r n b q k b . r\\np p p . . p p p\\n. . . . . n . .\\n. . . p p . . .\\n. . P . P . . .\\n. . . . . N . .\\nP P . P . P P P\\nR N B Q K B . R
'" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", - "\n", - "Oh, looks like we had a bit of miscommunication. You can't move the e7 pawn to e6 since you already have a pawn there. The board indicates that your previous move was d7 to d5. Perhaps you might consider developing your knights or your other pieces. Please provide a different legal move, PlayerBlack.\n", + "Ah, the Evergreen Game is certainly a classic! I also have a soft spot for the \"Immortal Game\" played by Adolf Anderssen and Lionel Kieseritzky in 1851. Anderssen's bold sacrifices opened the way to a king hunt that is still talked about today. It's always fascinating to witness sheer creativity on the board. Now, let's see how our own game unfolds!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", "\n", - "Oh, looks like we had a bit of miscommunication. You can't move the e7 pawn to e6 since you already have a pawn there. The board indicates that your previous move was d7 to d5. Perhaps you might consider developing your knights or your other pieces. Please provide a different legal move, PlayerBlack.\n", + "e2e4\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[34mStart a new chat with the following message: \n", - "Ah, the battlefield of mind is heating up already. We certainly can't have two pawns on the same square, now can we? In that case, let's get some more forces into the fray. How about I move my knight from b8 to c6? That will add a little support to my d5 pawn.\n", + "Ah, the Evergreen Game is certainly a classic! I also have a soft spot for the \"Immortal Game\" played by Adolf Anderssen and Lionel Kieseritzky in 1851. Anderssen's bold sacrifices opened the way to a king hunt that is still talked about today. It's always fascinating to witness sheer creativity on the board. Now, let's see how our own game unfolds!\n", "\n", - "So, here's my move: Nb8c6. Let the game continue!\n", - "\n", - "With the following carryover: \n", - "\u001b[0m\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", + "--------------------------------------------------------------------------------\n", + "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"e7e5\\n\\nIndeed, the Immortal Game was a great showcase of dynamic play and sacrificial strategy, definitely one to remember. Our board has its own story to write, let's see where the pieces lead us today. Lead on!\", 'goal': 'e2e4\\n\\nAh, the Evergreen Game is certainly a classic! I also have a soft spot for the \"Immortal Game\" played by Adolf Anderssen and Lionel Kieseritzky in 1851. Anderssen\\'s bold sacrifices opened the way to a king hunt that is still talked about today. It\\'s always fascinating to witness sheer creativity on the board. Now, let\\'s see how our own game unfolds!', 'carryover': []}\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "Ah, the battlefield of mind is heating up already. We certainly can't have two pawns on the same square, now can we? In that case, let's get some more forces into the fray. How about I move my knight from b8 to c6? That will add a little support to my d5 pawn.\n", + "e7e5\n", "\n", - "So, here's my move: Nb8c6. Let the game continue!\n", + "Indeed, the Immortal Game was a great showcase of dynamic play and sacrificial strategy, definitely one to remember. Our board has its own story to write, let's see where the pieces lead us today. Lead on!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", + "\n", + "e7e5\n", "\n", - "Error: illegal uci: 'b8c6' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "Indeed, the Immortal Game was a great showcase of dynamic play and sacrificial strategy, definitely one to remember. Our board has its own story to write, let's see where the pieces lead us today. Lead on!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"e2e4\\n\\nAh, the King's Pawn opening, a classic pathway to many memorable games. From the duel of minds to the clash of pieces, each move we make weaves the fabric of an intricate battle. Your move, PlayerBlack!\", 'goal': \"e7e5\\n\\nIndeed, the Immortal Game was a great showcase of dynamic play and sacrificial strategy, definitely one to remember. Our board has its own story to write, let's see where the pieces lead us today. Lead on!\", 'carryover': []}\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", - "My apologies for the confusion; it seems I made a slight error in my communication. Instead of knight to c6, I'm going to make a more modest move to ensure we're progressing with legal plays. I'll move my knight to d7 to support my pawn a bit more.\n", + "e2e4\n", "\n", - "Here's my move: Nb8d7. Your move, PlayerWhite!\n", + "Ah, the King's Pawn opening, a classic pathway to many memorable games. From the duel of minds to the clash of pieces, each move we make weaves the fabric of an intricate battle. Your move, PlayerBlack!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", + "\n", + "e2e4\n", "\n", - "Error: invalid uci: 'nb8d7'\n", + "Ah, the King's Pawn opening, a classic pathway to many memorable games. From the duel of minds to the clash of pieces, each move we make weaves the fabric of an intricate battle. Your move, PlayerBlack!\n", "\n", "--------------------------------------------------------------------------------\n", + "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"e7e5\\n\\nAh, mirroring the King's Pawn with quiet symmetry, we set the stage for the dance of knights and bishops. Let the melody of strategic conflict begin! Your move, PlayerWhite.\", 'goal': \"e2e4\\n\\nAh, the King's Pawn opening, a classic pathway to many memorable games. From the duel of minds to the clash of pieces, each move we make weaves the fabric of an intricate battle. Your move, PlayerBlack!\", 'carryover': []}\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "Ah, I see my mistake. Let's correct that! I'll get that knight into the game properly now. I'm moving my knight from g8 to f6.\n", + "e7e5\n", "\n", - "Here's the corrected move: Ng8f6. It's up to you now, PlayerWhite. Let's see what you have planned!\n", + "Ah, mirroring the King's Pawn with quiet symmetry, we set the stage for the dance of knights and bishops. Let the melody of strategic conflict begin! Your move, PlayerWhite.\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", - "\n", - "Error: illegal uci: 'g8f6' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "e7e5\n", "\n", - "Whoops! It seems I got ahead of myself. The knight on g8 has already been repositioned. Let me recalibrate my strategy here.\n", + "Ah, mirroring the King's Pawn with quiet symmetry, we set the stage for the dance of knights and bishops. Let the melody of strategic conflict begin! Your move, PlayerWhite.\n", "\n", - "In that case, the move will be to develop my dark-squared bishop by moving it to e7:\n", + "--------------------------------------------------------------------------------\n", + "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"e2e4\\n\\nThe center is the heart of the battlefield, and our pawns have just shaken hands there. I hope you're ready for a fierce and friendly contest, PlayerBlack!\", 'goal': \"e7e5\\n\\nAh, mirroring the King's Pawn with quiet symmetry, we set the stage for the dance of knights and bishops. Let the melody of strategic conflict begin! Your move, PlayerWhite.\", 'carryover': []}\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", - "Move: Bf8e7\n", + "e2e4\n", "\n", - "A quiet move for a silent attack. Your turn, PlayerWhite!\n", + "The center is the heart of the battlefield, and our pawns have just shaken hands there. I hope you're ready for a fierce and friendly contest, PlayerBlack!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", + "\n", + "e2e4\n", "\n", - "Error: illegal uci: 'f8e7' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "The center is the heart of the battlefield, and our pawns have just shaken hands there. I hope you're ready for a fierce and friendly contest, PlayerBlack!\n", "\n", "--------------------------------------------------------------------------------\n", + "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"Oh, a classic opening move! The King's Pawn opens the door to the mighty royals behind. Let's mirror that with energy and style. e7e5\\n\\nAnd may our battle be as graceful and fluid as a dance!\", 'goal': \"e2e4\\n\\nThe center is the heart of the battlefield, and our pawns have just shaken hands there. I hope you're ready for a fierce and friendly contest, PlayerBlack!\", 'carryover': []}\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "Thank you for your patience. It's time to make a move that abides by the board's current state.\n", + "Oh, a classic opening move! The King's Pawn opens the door to the mighty royals behind. Let's mirror that with energy and style. e7e5\n", "\n", - "It's my move as black, so let's strike a central note. I'll move my pawn from d5 to e4, challenging the center directly.\n", - "\n", - "Here's the official notation: d5e4.\n", - "\n", - "A pawn exchange might be in the future! What do you say, PlayerWhite?\n", + "And may our battle be as graceful and fluid as a dance!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", - "\n", - "Error: illegal uci: 'd5e4' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", - "\n", - "My apologies once again for the confusion. Let's ensure we have the right move this time. I see now that it is your move, PlayerWhite. Please proceed with your move, and then I'll respond accordingly.\n", + "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "Oh, a classic opening move! The King's Pawn opens the door to the mighty royals behind. Let's mirror that with energy and style. e7e5\n", "\n", - "Error: illegal uci: 'e2e4' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "And may our battle be as graceful and fluid as a dance!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"Indeed, the King's Pawn opening leads to some of the game's most fascinating dances. Let's step into the rhythm with Nf3, putting the pressure on your e5 pawn.\\n\\nNf3\\n\\nThe board feels alive with possibilities, doesn't it? Let the games begin!\", 'goal': \"Oh, a classic opening move! The King's Pawn opens the door to the mighty royals behind. Let's mirror that with energy and style. e7e5\\n\\nAnd may our battle be as graceful and fluid as a dance!\", 'carryover': []}\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", - "Understood, it looks like we've had a little mix-up, but now we're back on track. It's your move, PlayerWhite, as you have the white pieces and it's your turn. Once you've made your move, I'll respond as Black. Let the game continue.\n", + "Indeed, the King's Pawn opening leads to some of the game's most fascinating dances. Let's step into the rhythm with Nf3, putting the pressure on your e5 pawn.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "Nf3\n", "\n", - "Error: expected uci string to be of length 4 or 5: 'Your message does not contain a specific chess move. Please tell me your move in standard algebraic notation or describe the move you would like to make, and I will translate it into a UCI move string.'\n", + "The board feels alive with possibilities, doesn't it? Let the games begin!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", "\n", - "I apologize for the confusion earlier. Now that we have clarified whose turn it is, you may proceed with your move as PlayerWhite. Once you have made your move, I will respond accordingly as PlayerBlack. Your move, PlayerWhite.\n", + "Indeed, the King's Pawn opening leads to some of the game's most fascinating dances. Let's step into the rhythm with Nf3, putting the pressure on your e5 pawn.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "Nf3\n", "\n", - "Error: illegal uci: 'e2e4' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "The board feels alive with possibilities, doesn't it? Let the games begin!\n", "\n", "--------------------------------------------------------------------------------\n", + "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"Absolutely! The anticipation with each piece's potential is part of the charm of chess. Let's match your control of the center with...\\n\\nd6\\n\\nI'm developing a piece and supporting e5. The real dance is just starting!\", 'goal': \"Indeed, the King's Pawn opening leads to some of the game's most fascinating dances. Let's step into the rhythm with Nf3, putting the pressure on your e5 pawn.\\n\\nNf3\\n\\nThe board feels alive with possibilities, doesn't it? Let the games begin!\", 'carryover': []}\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "The current board position reflects that the last move made was e4 from PlayerWhite. As PlayerBlack, it is now my turn to make a move. I am going to develop my bishop to e7.\n", - "\n", - "Here's my move: Bf8e7\n", + "Absolutely! The anticipation with each piece's potential is part of the charm of chess. Let's match your control of the center with...\n", "\n", - "Now it's your turn, PlayerWhite. Let's see how you will carry on the battle from here!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "d6\n", "\n", - "Error: illegal uci: 'f8e7' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "I'm developing a piece and supporting e5. The real dance is just starting!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", - "\n", - "My apologies for the confusion earlier. It appears to be my turn as PlayerBlack.\n", - "\n", - "For my move, I'll develop my bishop to e6 to support my center pawn.\n", - "\n", - "Here's my move: Bf8e6\n", + "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", "\n", - "Your board, PlayerWhite. How will you respond to this development?\n", + "Absolutely! The anticipation with each piece's potential is part of the charm of chess. Let's match your control of the center with...\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "d6\n", "\n", - "Error: illegal uci: 'f8e6' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "I'm developing a piece and supporting e5. The real dance is just starting!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"Indeed, the center is a stage, and every move is part of the intricate ballet. I'll advance a pawn to keep up with the performance...\\n\\ne4\\n\\nI'm claiming some central space and opening lines for my pieces to join the dance. Let's see how our strategies will entangle!\", 'goal': \"Absolutely! The anticipation with each piece's potential is part of the charm of chess. Let's match your control of the center with...\\n\\nd6\\n\\nI'm developing a piece and supporting e5. The real dance is just starting!\", 'carryover': []}\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", - "It seems there has been a bit of confusion, but no worries. PlayerWhite, since you have the move and the pieces are all in their initial positions except for the pawn move to e4 and my knight move to f6, I await your move. Please proceed, PlayerWhite.\n", + "Indeed, the center is a stage, and every move is part of the intricate ballet. I'll advance a pawn to keep up with the performance...\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "e4\n", "\n", - "Error: illegal uci: 'e2e4' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "I'm claiming some central space and opening lines for my pieces to join the dance. Let's see how our strategies will entangle!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", "\n", - "I acknowledge the current board state with your e4 move and my knight already positioned at f6. As it is your move, PlayerWhite, please go ahead and I'll respond thereafter.\n", + "Indeed, the center is a stage, and every move is part of the intricate ballet. I'll advance a pawn to keep up with the performance...\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "e4\n", "\n", - "Error: illegal uci: 'e2e4' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "I'm claiming some central space and opening lines for my pieces to join the dance. Let's see how our strategies will entangle!\n", "\n", "--------------------------------------------------------------------------------\n", + "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"A bold opening move by PlayerWhite, thrusting into the center with 1.e4. The King's Pawn opening, an invitation to countless classical duels. Ah, but the dance shall indeed be lively!\\n\\nLet me take your hand and join this ballet with... \\n\\ne5\\n\\nAn echo in the dark, mirroring your step, extending an invitation to all the pieces behind to step into the light. The symmetry is pleasing to the eye, but how long before it breaks?\", 'goal': \"Indeed, the center is a stage, and every move is part of the intricate ballet. I'll advance a pawn to keep up with the performance...\\n\\ne4\\n\\nI'm claiming some central space and opening lines for my pieces to join the dance. Let's see how our strategies will entangle!\", 'carryover': []}\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "My deepest apologies for the oversight on the current position earlier. It is indeed your move as white, PlayerWhite.\n", + "A bold opening move by PlayerWhite, thrusting into the center with 1.e4. The King's Pawn opening, an invitation to countless classical duels. Ah, but the dance shall indeed be lively!\n", "\n", - "Considering the current board setup, it is clear that both players have only made a single pawn move to e4 and e5 respectively, and white's knight is developed to f3, whereas black's knight has been played to f6.\n", + "Let me take your hand and join this ballet with... \n", "\n", - "Now, it is white's turn. Please make your next move, PlayerWhite.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "e5\n", "\n", - "Error: illegal uci: 'e4e5' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "An echo in the dark, mirroring your step, extending an invitation to all the pieces behind to step into the light. The symmetry is pleasing to the eye, but how long before it breaks?\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", "\n", - "Thank you for the update. It appears we had a miscommunication about the board state. Now, with the correct position in front of us, I see that it is indeed your move as White.\n", + "A bold opening move by PlayerWhite, thrusting into the center with 1.e4. The King's Pawn opening, an invitation to countless classical duels. Ah, but the dance shall indeed be lively!\n", "\n", - "With your pieces poised and ready, please proceed with your next move, PlayerWhite.\n", + "Let me take your hand and join this ballet with... \n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "e5\n", "\n", - "Error: expected uci string to be of length 4 or 5: 'I\\'m sorry, but you haven\\'t provided any specific move to translate into UCI format. Please provide the move you\\'d like to make, such as \"pawn from e2 to e4\" or \"knight from g1 to f3\", and I\\'ll translate it for you.'\n", + "An echo in the dark, mirroring your step, extending an invitation to all the pieces behind to step into the light. The symmetry is pleasing to the eye, but how long before it breaks?\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"Ah, the classic response, PlayerBlack! The King's Pawn opening often leads us down the well-trodden paths of Ruy López, Italians, and Sicilian Defenses, each with its own flavor and intrigue. But for now, let us see where our pawns take us.\\n\\nAs the sun greets the dawn, I shall bring forth the Knight, a brave steed venturing into the fray.\\n\\nNf3\\n\\nThe knight boldly extends its hand, prancing forward with nobility and grace. A gentle joust awaits; may it be in the spirit of fair play and sharp wit!\", 'goal': \"A bold opening move by PlayerWhite, thrusting into the center with 1.e4. The King's Pawn opening, an invitation to countless classical duels. Ah, but the dance shall indeed be lively!\\n\\nLet me take your hand and join this ballet with... \\n\\ne5\\n\\nAn echo in the dark, mirroring your step, extending an invitation to all the pieces behind to step into the light. The symmetry is pleasing to the eye, but how long before it breaks?\", 'carryover': []}\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", - "Understood. We are now clear on the board state. As it appears to be White's turn, I will await your move, PlayerWhite. Please provide your move in standard UCI format when you are ready.\n", + "Ah, the classic response, PlayerBlack! The King's Pawn opening often leads us down the well-trodden paths of Ruy López, Italians, and Sicilian Defenses, each with its own flavor and intrigue. But for now, let us see where our pawns take us.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", - "\n", - "Error: expected uci string to be of length 4 or 5: 'Your message did not include a specific move in UCI format for me to extract. Please provide a move in natural language or direct UCI format for me to translate.'\n", + "As the sun greets the dawn, I shall bring forth the Knight, a brave steed venturing into the fray.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "Nf3\n", "\n", - "Ah, now that we're on the same page about the board state, it's your move, PlayerWhite. Once you make your move, I'll plan my response. The game awaits your play! Shall we venture forth into the middlegame?\n", + "The knight boldly extends its hand, prancing forward with nobility and grace. A gentle joust awaits; may it be in the spirit of fair play and sharp wit!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", "\n", - "Error: illegal uci: 'e2e4' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "Ah, the classic response, PlayerBlack! The King's Pawn opening often leads us down the well-trodden paths of Ruy López, Italians, and Sicilian Defenses, each with its own flavor and intrigue. But for now, let us see where our pawns take us.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", - "\n", - "Acknowledging the board state, it's White's turn. PlayerWhite, I await your move. The pieces are in tension, eager to advance. What shall be your strategy? Proceed with your move.\n", + "As the sun greets the dawn, I shall bring forth the Knight, a brave steed venturing into the fray.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "Nf3\n", "\n", - "Error: expected uci string to be of length 4 or 5: 'I need specific details of the move you want to make in order to provide a UCI move string. Please tell me the move you\\'d like to make, such as \"Move pawn from e2 to e4\" or \"Knight from g1 to f3.\"'\n", + "The knight boldly extends its hand, prancing forward with nobility and grace. A gentle joust awaits; may it be in the spirit of fair play and sharp wit!\n", "\n", "--------------------------------------------------------------------------------\n", + "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"Indeed, PlayerWhite! The knight's leap sets a tone of elegance and finesse. But as the twilight calls to the ebony army, I find it fitting to introduce my own knight to the center stage.\\n\\nNc6\\n\\nJust as your steed claims its presence, mine rises to meet the challenge, promising a dance of knights none shall soon forget. May our contest be both merry and fierce!\", 'goal': \"Ah, the classic response, PlayerBlack! The King's Pawn opening often leads us down the well-trodden paths of Ruy López, Italians, and Sicilian Defenses, each with its own flavor and intrigue. But for now, let us see where our pawns take us.\\n\\nAs the sun greets the dawn, I shall bring forth the Knight, a brave steed venturing into the fray.\\n\\nNf3\\n\\nThe knight boldly extends its hand, prancing forward with nobility and grace. A gentle joust awaits; may it be in the spirit of fair play and sharp wit!\", 'carryover': []}\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "As the board currently stands, it is indeed White's (your) move. I'm looking forward to seeing how you plan to shape the game from here. Please make your move, PlayerWhite.\n", + "Indeed, PlayerWhite! The knight's leap sets a tone of elegance and finesse. But as the twilight calls to the ebony army, I find it fitting to introduce my own knight to the center stage.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "Nc6\n", "\n", - "Error: illegal uci: 'e2e4' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "Just as your steed claims its presence, mine rises to meet the challenge, promising a dance of knights none shall soon forget. May our contest be both merry and fierce!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", "\n", - "Ah, now that the board state is clear and it's your move as white, PlayerWhite, let's see how you'll advance the pieces. Take your time and make your play.\n", + "Indeed, PlayerWhite! The knight's leap sets a tone of elegance and finesse. But as the twilight calls to the ebony army, I find it fitting to introduce my own knight to the center stage.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "Nc6\n", "\n", - "Error: expected uci string to be of length 4 or 5: 'Your instruction does not contain specific details on the move to be made. Please provide the move you would like translated into UCI format. For instance, \"move the pawn from e2 to e4\" or \"knight from g1 to f3\".'\n", + "Just as your steed claims its presence, mine rises to meet the challenge, promising a dance of knights none shall soon forget. May our contest be both merry and fierce!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", - "\n", - "It appears there has been a continuous miscommunication regarding the moves. However, based on our current understanding of the board, it is indeed PlayerWhite's turn.\n", + "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"Ah, PlayerBlack, the symmetry of our knights' gallant endeavors indeed foreshadows a ballet of tactical prowess on this checkered stage. But as the spotlight widens, I shall call upon the silent potential of the pawns.\\n\\nd4\\n\\nAdvancing my pawn to stake a claim in the center, I beckon the pawn phalanx to awake from their slumber. Onward we march, to rhythm and reason, in this grand theater of war and wit!\", 'goal': \"Indeed, PlayerWhite! The knight's leap sets a tone of elegance and finesse. But as the twilight calls to the ebony army, I find it fitting to introduce my own knight to the center stage.\\n\\nNc6\\n\\nJust as your steed claims its presence, mine rises to meet the challenge, promising a dance of knights none shall soon forget. May our contest be both merry and fierce!\", 'carryover': []}\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", - "To reiterate, the only moves that have been made are:\n", + "Ah, PlayerBlack, the symmetry of our knights' gallant endeavors indeed foreshadows a ballet of tactical prowess on this checkered stage. But as the spotlight widens, I shall call upon the silent potential of the pawns.\n", "\n", - "1. White has moved their pawn to e4.\n", - "2. Black has moved their pawn to e5.\n", - "3. White has moved their knight to f3.\n", - "4. Black has moved their knight to f6.\n", + "d4\n", "\n", - "Now the board is set, and it's your move, PlayerWhite. Could you please provide your move in the universal chess interface (UCI) format, which should consist of four characters (e.g., \"e2e4\")? If you choose to castle, it should be written as \"e1g1\" for kingside or \"e1c1\" for queenside. Please proceed with your move.\n", + "Advancing my pawn to stake a claim in the center, I beckon the pawn phalanx to awake from their slumber. Onward we march, to rhythm and reason, in this grand theater of war and wit!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", "\n", - "Error: illegal uci: 'e4e5' in rnbqkb1r/ppp2ppp/5n2/3pp3/2P1P3/5N2/PP1P1PPP/RNBQKB1R w KQkq - 1 4\n", + "Ah, PlayerBlack, the symmetry of our knights' gallant endeavors indeed foreshadows a ballet of tactical prowess on this checkered stage. But as the spotlight widens, I shall call upon the silent potential of the pawns.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "d4\n", "\n", - "With the current state of the board in front of us, it is indeed White's move. PlayerWhite, please make your move, and I will then respond as Black.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", - "\n", - "With the current state of the board in front of us, it is indeed White's move. PlayerWhite, please make your move, and I will then respond as Black.\n", + "Advancing my pawn to stake a claim in the center, I beckon the pawn phalanx to awake from their slumber. Onward we march, to rhythm and reason, in this grand theater of war and wit!\n", "\n", "--------------------------------------------------------------------------------\n" ] @@ -1032,10 +721,10 @@ { "data": { "text/plain": [ - "ChatResult(chat_history=[{'content': 'Your turn.', 'role': 'assistant'}, {'content': \"Ah, my apologies, it's your move to set the stage. Take it away, PlayerWhite!\", 'role': 'user'}, {'content': \"The board indicates that my first move, e4, has been played. It's your turn PlayerBlack. Please make your move.\", 'role': 'assistant'}, {'content': \"Ah, I see. Then let's continue this classic ballet of chess with a typical response:\\n\\nng1f3\\n\\nYour move!\", 'role': 'user'}, {'content': 'd7d5\\n\\nAll right, the dance continues as the pawns stride forward. Your move, PlayerWhite!', 'role': 'assistant'}, {'content': \"c2c4\\n\\nCharging forward with the queen's gambit! The board's gauntlet has been thrown; do you accept the challenge or have another plan up your sleeve, PlayerBlack?\", 'role': 'user'}, {'content': \"Oh, looks like we had a bit of miscommunication. You can't move the e7 pawn to e6 since you already have a pawn there. The board indicates that your previous move was d7 to d5. Perhaps you might consider developing your knights or your other pieces. Please provide a different legal move, PlayerBlack.\", 'role': 'assistant'}, {'content': \"With the current state of the board in front of us, it is indeed White's move. PlayerWhite, please make your move, and I will then respond as Black.\", 'role': 'user'}], summary=\"With the current state of the board in front of us, it is indeed White's move. PlayerWhite, please make your move, and I will then respond as Black.\", cost=({'total_cost': 1.61226, 'gpt-4': {'cost': 1.61226, 'prompt_tokens': 48530, 'completion_tokens': 2606, 'total_tokens': 51136}}, {'total_cost': 1.5953100000000002, 'gpt-4': {'cost': 1.5953100000000002, 'prompt_tokens': 48055, 'completion_tokens': 2561, 'total_tokens': 50616}}), human_input=[])" + "[ChatResult(chat_history=[{'content': 'Your turn.', 'role': 'assistant'}, {'content': \"Ah, my bad! I realise what's going on now - it's White's move first! PlayerWhite, go ahead, it's your turn.\", 'role': 'user'}, {'content': \"Ah, it seems I may have attempted to re-play my first move. My apologies for the oversight. It's your turn, PlayerBlack. The board is set for your response.\", 'role': 'assistant'}, {'content': 'No problem at all, PlayerWhite, it happens to the best of us! Let\\'s have a good game, shall we?\\n\\nI\\'ll make my move now. I\\'ll go for the classic and solid \"e7 to e5\".\\n\\ne7e5', 'role': 'user'}, {'content': \"Ah, the classic King's Pawn Opening. A great choice to start things off! I'll go with a knight's leap, always aiming to control the center.\\n\\ng1f3\\n\\nLet the game advance in the spirit of good sportsmanship!\", 'role': 'assistant'}, {'content': \"It's always a pleasure to see classical play! I'll meet you in the center with my pawn.\\n\\ne7e5\\n\\nMay our battle be as enthralling as the great matches of the past!\", 'role': 'user'}, {'content': \"Indeed, the central tension is the essence of the game! Let's get our pieces ready for the upcoming symphony of moves.\\n\\ne2e4\\n\\nTalking about great matches, which classic chess game comes to your mind first?\", 'role': 'assistant'}, {'content': 'e7e5\\n\\nOh, for classical games, it has to be the \"Evergreen Game\" between Adolf Anderssen and Jean Dufresne. The way Anderssen conducted a dazzling combination to win has always been an inspiration. How about you? What\\'s your favorite historical masterpiece on the 64 squares?', 'role': 'user'}, {'content': 'e2e4\\n\\nAh, the Evergreen Game is certainly a classic! I also have a soft spot for the \"Immortal Game\" played by Adolf Anderssen and Lionel Kieseritzky in 1851. Anderssen\\'s bold sacrifices opened the way to a king hunt that is still talked about today. It\\'s always fascinating to witness sheer creativity on the board. Now, let\\'s see how our own game unfolds!', 'role': 'assistant'}, {'content': \"e7e5\\n\\nIndeed, the Immortal Game was a great showcase of dynamic play and sacrificial strategy, definitely one to remember. Our board has its own story to write, let's see where the pieces lead us today. Lead on!\", 'role': 'user'}, {'content': \"e2e4\\n\\nAh, the King's Pawn opening, a classic pathway to many memorable games. From the duel of minds to the clash of pieces, each move we make weaves the fabric of an intricate battle. Your move, PlayerBlack!\", 'role': 'assistant'}, {'content': \"e7e5\\n\\nAh, mirroring the King's Pawn with quiet symmetry, we set the stage for the dance of knights and bishops. Let the melody of strategic conflict begin! Your move, PlayerWhite.\", 'role': 'user'}, {'content': \"e2e4\\n\\nThe center is the heart of the battlefield, and our pawns have just shaken hands there. I hope you're ready for a fierce and friendly contest, PlayerBlack!\", 'role': 'assistant'}, {'content': \"Oh, a classic opening move! The King's Pawn opens the door to the mighty royals behind. Let's mirror that with energy and style. e7e5\\n\\nAnd may our battle be as graceful and fluid as a dance!\", 'role': 'user'}, {'content': \"Indeed, the King's Pawn opening leads to some of the game's most fascinating dances. Let's step into the rhythm with Nf3, putting the pressure on your e5 pawn.\\n\\nNf3\\n\\nThe board feels alive with possibilities, doesn't it? Let the games begin!\", 'role': 'assistant'}, {'content': \"Absolutely! The anticipation with each piece's potential is part of the charm of chess. Let's match your control of the center with...\\n\\nd6\\n\\nI'm developing a piece and supporting e5. The real dance is just starting!\", 'role': 'user'}, {'content': \"Indeed, the center is a stage, and every move is part of the intricate ballet. I'll advance a pawn to keep up with the performance...\\n\\ne4\\n\\nI'm claiming some central space and opening lines for my pieces to join the dance. Let's see how our strategies will entangle!\", 'role': 'assistant'}, {'content': \"A bold opening move by PlayerWhite, thrusting into the center with 1.e4. The King's Pawn opening, an invitation to countless classical duels. Ah, but the dance shall indeed be lively!\\n\\nLet me take your hand and join this ballet with... \\n\\ne5\\n\\nAn echo in the dark, mirroring your step, extending an invitation to all the pieces behind to step into the light. The symmetry is pleasing to the eye, but how long before it breaks?\", 'role': 'user'}, {'content': \"Ah, the classic response, PlayerBlack! The King's Pawn opening often leads us down the well-trodden paths of Ruy López, Italians, and Sicilian Defenses, each with its own flavor and intrigue. But for now, let us see where our pawns take us.\\n\\nAs the sun greets the dawn, I shall bring forth the Knight, a brave steed venturing into the fray.\\n\\nNf3\\n\\nThe knight boldly extends its hand, prancing forward with nobility and grace. A gentle joust awaits; may it be in the spirit of fair play and sharp wit!\", 'role': 'assistant'}, {'content': \"Indeed, PlayerWhite! The knight's leap sets a tone of elegance and finesse. But as the twilight calls to the ebony army, I find it fitting to introduce my own knight to the center stage.\\n\\nNc6\\n\\nJust as your steed claims its presence, mine rises to meet the challenge, promising a dance of knights none shall soon forget. May our contest be both merry and fierce!\", 'role': 'user'}, {'content': \"Ah, PlayerBlack, the symmetry of our knights' gallant endeavors indeed foreshadows a ballet of tactical prowess on this checkered stage. But as the spotlight widens, I shall call upon the silent potential of the pawns.\\n\\nd4\\n\\nAdvancing my pawn to stake a claim in the center, I beckon the pawn phalanx to awake from their slumber. Onward we march, to rhythm and reason, in this grand theater of war and wit!\", 'role': 'assistant'}], summary=\"Ah, PlayerBlack, the symmetry of our knights' gallant endeavors indeed foreshadows a ballet of tactical prowess on this checkered stage. But as the spotlight widens, I shall call upon the silent potential of the pawns.\\n\\nd4\\n\\nAdvancing my pawn to stake a claim in the center, I beckon the pawn phalanx to awake from their slumber. Onward we march, to rhythm and reason, in this grand theater of war and wit!\", cost=({'total_cost': 0.39755999999999997, 'gpt-4': {'cost': 0.39755999999999997, 'prompt_tokens': 10442, 'completion_tokens': 1405, 'total_tokens': 11847}}, {'total_cost': 0.39171, 'gpt-4': {'cost': 0.39171, 'prompt_tokens': 10279, 'completion_tokens': 1389, 'total_tokens': 11668}}), human_input=[])]" ] }, - "execution_count": 8, + "execution_count": 4, "metadata": {}, "output_type": "execute_result" } @@ -1056,29 +745,17 @@ " return True, None\n", "\n", "\n", - "def board_chat_func(chat_queue, recipient, messages, sender, config):\n", - " # TODO: better way to handle this\n", - " if chat_queue[0][\"recipient\"].reply_num >= max_turn:\n", - " return True, None\n", - " c = chat_queue[0] # board = config\n", - " board = c[\"recipient\"].board\n", + "def player2board_init_message(recipient, messages, sender, config):\n", + " board = config\n", " board_state_msg = [{\"role\": \"system\", \"content\": f\"Current board:\\n{board}\"}]\n", " useful_msg = messages[-1].copy()\n", " useful_msg[\"content\"] = useful_msg.get(\"content\", \"\") + f\"The current board is:\\n {board} .\"\n", - " # oai_messages = [messages[-1]]\n", - " # messages[-1] = useful_msg\n", - " _, message = recipient.generate_oai_reply([useful_msg] + board_state_msg, sender)\n", - " c[\"message\"] = message\n", - " # c[\"carryover\"] = f\"Current board:\\n{board}\" # NOTE: in the old code, this is added as system message\n", - " chat_queue[0] = c\n", - " # print(\"nessss\")\n", - " res = recipient.initiate_chats(chat_queue)\n", - " last_res = list(res.values())[-1]\n", - " return True, last_res.summary\n", + " oai_messages = [messages[-1]]\n", + " _, message = recipient.generate_oai_reply(oai_messages + board_state_msg, sender)\n", + " return message\n", "\n", "\n", "def board_reply(recipient, messages, sender, config):\n", - " # print(\"resplyssss\")\n", " if recipient.reply_num >= max_turn:\n", " return True, None\n", " org_msg = messages[-1].copy()\n", @@ -1087,13 +764,20 @@ " # TODO: better way to handle this\n", " message[\"content\"] = \"Extract a UCI move from the following message \\n.\" + message.get(\"content\", \"\")\n", " _, reply = recipient.generate_oai_reply([message], sender)\n", + " # reply = recipient.generate_reply(recipient.correct_move_messages[sender] + [message], sender)\n", " uci_move = reply if isinstance(reply, str) else str(reply[\"content\"])\n", " recipient.update_reply_num()\n", " try:\n", " recipient.board.push_uci(uci_move)\n", " except ValueError as e:\n", " # invalid move\n", - " return True, f\"Error: {e}\"\n", + " return (\n", + " True,\n", + " f\"Error: {e}\"\n", + " + f\"The current board is {recipient.board}\"\n", + " + \"\\n\\nTry again to respond to the following message as if nothing happened.\"\n", + " + sender.context[0].get(\"goal\", \"\"),\n", + " )\n", " else:\n", " # valid move\n", " m = chess.Move.from_uci(uci_move)\n", @@ -1105,31 +789,47 @@ " )\n", " except NameError as e:\n", " print(f\"Error displaying board: {e}\")\n", - " # better way to handle this\n", - " recipient.set_correct_move_messages(sender, message, uci_move)\n", - " recipient.correct_move_messages[sender][-1][\"role\"] = \"assistant\"\n", - " return True, org_msg.get(\"content\", \"\") # + \"\\n Move:\" + uci_move\n", + " # better way to handle this\n", + " recipient.set_correct_move_messages(sender, message, uci_move)\n", + " recipient.correct_move_messages[sender][-1][\"role\"] = \"assistant\"\n", + " return True, org_msg.get(\"content\", \"\") # + \"\\n Move:\" + uci_move\n", "\n", "\n", - "board_agent.register_reply([white_player_name, black_player_name], board_reply, 0)\n", "player_white.register_nested_chats(\n", " black_player_name,\n", - " [{\"recipient\": board_agent, \"summary_method\": \"last_msg\"}],\n", - " board_chat_func,\n", - " 0,\n", - " ignore_async_in_sync_chat=True,\n", + " [\n", + " {\n", + " \"sender\": player_white,\n", + " \"recipient\": board_agent,\n", + " \"init_message\": player2board_init_message,\n", + " \"summary_method\": \"last_msg\",\n", + " }\n", + " ],\n", + " config=board_agent.board,\n", ")\n", "player_black.register_nested_chats(\n", " white_player_name,\n", - " [{\"recipient\": board_agent, \"summary_method\": \"last_msg\"}],\n", - " board_chat_func,\n", - " 0,\n", - " ignore_async_in_sync_chat=True,\n", + " [\n", + " {\n", + " \"sender\": player_black,\n", + " \"recipient\": board_agent,\n", + " \"init_message\": player2board_init_message,\n", + " \"summary_method\": \"last_msg\",\n", + " }\n", + " ],\n", + " config=board_agent.board,\n", ")\n", "player_white.register_reply(BoardAgent, player2board_reply)\n", "player_black.register_reply(BoardAgent, player2board_reply)\n", - "\n", - "player_white.initiate_chat(player_black, message=\"Your turn.\")" + "board_agent.register_reply([white_player_name, black_player_name], board_reply, 0)\n", + "autogen.initiate_chats([{\"sender\": player_white, \"recipient\": player_black, \"message\": \"Your turn.\"}])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Register reply func and nested chats" ] } ], From 6495f9da1f72f43508f5c328039f1a6eb0fc12ae Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Wed, 14 Feb 2024 19:26:53 -0500 Subject: [PATCH 29/57] update notebook --- notebook/agentchat_nestedchat_chess.ipynb | 669 +++++++++++++--------- 1 file changed, 399 insertions(+), 270 deletions(-) diff --git a/notebook/agentchat_nestedchat_chess.ipynb b/notebook/agentchat_nestedchat_chess.ipynb index 98c4d648c3e..8256762004d 100644 --- a/notebook/agentchat_nestedchat_chess.ipynb +++ b/notebook/agentchat_nestedchat_chess.ipynb @@ -147,13 +147,11 @@ "name": "stdout", "output_type": "stream", "text": [ - "chat_infochat_info {'sender': , 'recipient': , 'message': 'Your turn.', 'carryover': []}\n", "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", "\n", "Your turn.\n", "\n", "--------------------------------------------------------------------------------\n", - "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"Alright, let's start this dance with a classic move.\\n\\ne7-e5\", 'goal': 'Your turn.', 'carryover': []}\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", "Alright, let's start this dance with a classic move.\n", @@ -163,28 +161,13 @@ "--------------------------------------------------------------------------------\n", "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", "\n", - "Error: illegal uci: 'e7e5' in rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1The current board is r n b q k b n r\n", - "p p p p p p p p\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - "P P P P P P P P\n", - "R N B Q K B N R\n", - "\n", - "Try again to respond to the following message as if nothing happened.Your turn.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "Error: illegal uci: 'e7e5' in rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\n", "\n", - "Oh, sorry about that hiccup. Let's get our pieces moving.\n", - "\n", - "g8f6\n", + "Your turn.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "The current board is \n", "\n", - "Error: illegal uci: 'g8f6' in rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1The current board is r n b q k b n r\n", + " r n b q k b n r\n", "p p p p p p p p\n", ". . . . . . . .\n", ". . . . . . . .\n", @@ -193,19 +176,16 @@ "P P P P P P P P\n", "R N B Q K B N R\n", "\n", - "Try again to respond to the following message as if nothing happened.Your turn.\n", - "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", "\n", - "Let's get this match underway.\n", + "Error: illegal uci: 'e7e5' in rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\n", "\n", - "e7e5\n", + "Your turn.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "The current board is \n", "\n", - "Error: illegal uci: 'e7e5' in rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1The current board is r n b q k b n r\n", + " r n b q k b n r\n", "p p p p p p p p\n", ". . . . . . . .\n", ". . . . . . . .\n", @@ -214,75 +194,58 @@ "P P P P P P P P\n", "R N B Q K B N R\n", "\n", - "Try again to respond to the following message as if nothing happened.Your turn.\n", - "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", - "\n", - "Ah, I see now. Well, if it's my turn, let's kick this off with a classic move.\n", - "\n", - "e7e5\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", - "Error: illegal uci: 'e7e5' in rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1The current board is r n b q k b n r\n", - "p p p p p p p p\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - "P P P P P P P P\n", - "R N B Q K B N R\n", + "e2e4\n", "\n", - "Try again to respond to the following message as if nothing happened.Your turn.\n", + "Starting with the classic king's pawn opening! Let's see where this game takes us, PlayerBlack!\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "data": { + "image/svg+xml": [ + "
r n b q k b n r\n",
+       "p p p p p p p p\n",
+       ". . . . . . . .\n",
+       ". . . . . . . .\n",
+       ". . . . P . . .\n",
+       ". . . . . . . .\n",
+       "P P P P . P P P\n",
+       "R N B Q K B N R
" + ], + "text/plain": [ + "'
r n b q k b n r\\np p p p p p p p\\n. . . . . . . .\\n. . . . . . . .\\n. . . . P . . .\\n. . . . . . . .\\nP P P P . P P P\\nR N B Q K B N R
'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", "\n", - "My apologies for the confusion earlier. Now, let's start things properly.\n", + "e2e4\n", "\n", - "1... e7e5\n", + "Starting with the classic king's pawn opening! Let's see where this game takes us, PlayerBlack!\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", "\n", - "Error: illegal uci: 'e7e5' in rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1The current board is r n b q k b n r\n", - "p p p p p p p p\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - "P P P P P P P P\n", - "R N B Q K B N R\n", + "e2e4\n", "\n", - "Try again to respond to the following message as if nothing happened.Your turn.\n", + "Starting with the classic king's pawn opening! Let's see where this game takes us, PlayerBlack!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "My apologies for the oversight, let's get the game on track.\n", - "\n", - "d7d5\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", - "\n", - "Error: illegal uci: 'd7d5' in rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1The current board is r n b q k b n r\n", - "p p p p p p p p\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - "P P P P P P P P\n", - "R N B Q K B N R\n", - "\n", - "Try again to respond to the following message as if nothing happened.Your turn.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "e7e5\n", "\n", - "Ah, my bad! I realise what's going on now - it's White's move first! PlayerWhite, go ahead, it's your turn.\n", + "I’ll match your opening move, PlayerWhite. The symmetrical dance begins!\n", "\n", "--------------------------------------------------------------------------------\n" ] @@ -291,16 +254,16 @@ "data": { "image/svg+xml": [ "
r n b q k b n r\n",
-       "p p p p p p p p\n",
-       ". . . . . . . .\n",
+       "p p p p . p p p\n",
        ". . . . . . . .\n",
+       ". . . . p . . .\n",
        ". . . . P . . .\n",
        ". . . . . . . .\n",
        "P P P P . P P P\n",
-       "R N B Q K B N R
" + "R N B Q K B N R" ], "text/plain": [ - "'
r n b q k b n r\\np p p p p p p p\\n. . . . . . . .\\n. . . . . . . .\\n. . . . P . . .\\n. . . . . . . .\\nP P P P . P P P\\nR N B Q K B N R
'" + "'
r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . . . .\\nP P P P . P P P\\nR N B Q K B N R
'" ] }, "metadata": {}, @@ -312,408 +275,574 @@ "text": [ "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", "\n", - "Ah, my bad! I realise what's going on now - it's White's move first! PlayerWhite, go ahead, it's your turn.\n", + "e7e5\n", + "\n", + "I’ll match your opening move, PlayerWhite. The symmetrical dance begins!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", "\n", - "Ah, my bad! I realise what's going on now - it's White's move first! PlayerWhite, go ahead, it's your turn.\n", + "e7e5\n", + "\n", + "I’ll match your opening move, PlayerWhite. The symmetrical dance begins!\n", "\n", "--------------------------------------------------------------------------------\n", - "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"No worries, PlayerBlack! Now, let's kick this off with classic King's Pawn opening.\\n\\ne2e4\", 'goal': \"Ah, my bad! I realise what's going on now - it's White's move first! PlayerWhite, go ahead, it's your turn.\", 'carryover': []}\n", "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", - "No worries, PlayerBlack! Now, let's kick this off with classic King's Pawn opening.\n", - "\n", "e2e4\n", "\n", + "Indeed, PlayerBlack, a mirror reflection to start our game. May the best strategist lead the dance to victory!\n", + "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", "\n", - "Error: illegal uci: 'e2e4' in rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1The current board is r n b q k b n r\n", - "p p p p p p p p\n", - ". . . . . . . .\n", + "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", + "\n", + "e7e5\n", + "\n", + "I’ll match your opening move, PlayerWhite. The symmetrical dance begins!\n", + "\n", + "The current board is \n", + "\n", + " r n b q k b n r\n", + "p p p p . p p p\n", ". . . . . . . .\n", + ". . . . p . . .\n", ". . . . P . . .\n", ". . . . . . . .\n", "P P P P . P P P\n", "R N B Q K B N R\n", "\n", - "Try again to respond to the following message as if nothing happened.Ah, my bad! I realise what's going on now - it's White's move first! PlayerWhite, go ahead, it's your turn.\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", + "\n", + "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", + "\n", + "e7e5\n", + "\n", + "I’ll match your opening move, PlayerWhite. The symmetrical dance begins!\n", + "\n", + "The current board is \n", + "\n", + " r n b q k b n r\n", + "p p p p . p p p\n", + ". . . . . . . .\n", + ". . . . p . . .\n", + ". . . . P . . .\n", + ". . . . . . . .\n", + "P P P P . P P P\n", + "R N B Q K B N R\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "Haha, seems we had a little mix-up there, PlayerBlack. Alright, let's get down to the real business – opening move for real this time.\n", + "Whoops, seems like my move overlapped your pawn's initial step PlayerWhite. Let's try my move again:\n", "\n", - "e2e4\n", + "e7e5\n", + "\n", + "The game continues smoothly now. The pawns face off, each with dreams of becoming more than they are now—a true battlefield romance, wouldn't you say?\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", "\n", - "Error: illegal uci: 'e2e4' in rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1The current board is r n b q k b n r\n", - "p p p p p p p p\n", - ". . . . . . . .\n", + "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", + "\n", + "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", + "\n", + "e7e5\n", + "\n", + "I’ll match your opening move, PlayerWhite. The symmetrical dance begins!\n", + "\n", + "The current board is \n", + "\n", + " r n b q k b n r\n", + "p p p p . p p p\n", ". . . . . . . .\n", + ". . . . p . . .\n", ". . . . P . . .\n", ". . . . . . . .\n", "P P P P . P P P\n", "R N B Q K B N R\n", "\n", - "Try again to respond to the following message as if nothing happened.Ah, my bad! I realise what's going on now - it's White's move first! PlayerWhite, go ahead, it's your turn.\n", + "The current board is \n", + "\n", + " r n b q k b n r\n", + "p p p p . p p p\n", + ". . . . . . . .\n", + ". . . . p . . .\n", + ". . . . P . . .\n", + ". . . . . . . .\n", + "P P P P . P P P\n", + "R N B Q K B N R\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", "\n", - "May the chessboard be our canvas. Let's start with something creative. How about a knight's leap?\n", + "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", "\n", - "g1f3\n", + "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "e7e5\n", "\n", - "Error: illegal uci: 'g1f3' in rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq - 0 1The current board is r n b q k b n r\n", - "p p p p p p p p\n", - ". . . . . . . .\n", + "I’ll match your opening move, PlayerWhite. The symmetrical dance begins!\n", + "\n", + "The current board is \n", + "\n", + " r n b q k b n r\n", + "p p p p . p p p\n", ". . . . . . . .\n", + ". . . . p . . .\n", ". . . . P . . .\n", ". . . . . . . .\n", "P P P P . P P P\n", "R N B Q K B N R\n", "\n", - "Try again to respond to the following message as if nothing happened.Ah, my bad! I realise what's going on now - it's White's move first! PlayerWhite, go ahead, it's your turn.\n", + "The current board is \n", + "\n", + " r n b q k b n r\n", + "p p p p . p p p\n", + ". . . . . . . .\n", + ". . . . p . . .\n", + ". . . . P . . .\n", + ". . . . . . . .\n", + "P P P P . P P P\n", + "R N B Q K B N R\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", - "Ah, it seems I may have attempted to re-play my first move. My apologies for the oversight. It's your turn, PlayerBlack. The board is set for your response.\n", + "e2e4\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", + "Best by test, as they say! Let's see where this classic opening takes us today, PlayerBlack. Your move! \n", "\n", - "Ah, it seems I may have attempted to re-play my first move. My apologies for the oversight. It's your turn, PlayerBlack. The board is set for your response.\n", + "The current board is \n", + "\n", + " r n b q k b n r\n", + "p p p p . p p p\n", + ". . . . . . . .\n", + ". . . . . . . .\n", + ". . . . P . . .\n", + ". . . . . . . .\n", + "P P P P . P P P\n", + "R N B Q K B N R\n", "\n", "--------------------------------------------------------------------------------\n", - "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': 'No problem at all, PlayerWhite, it happens to the best of us! Let\\'s have a good game, shall we?\\n\\nI\\'ll make my move now. I\\'ll go for the classic and solid \"e7 to e5\".\\n\\ne7e5', 'goal': \"Ah, it seems I may have attempted to re-play my first move. My apologies for the oversight. It's your turn, PlayerBlack. The board is set for your response.\", 'carryover': []}\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", "\n", - "No problem at all, PlayerWhite, it happens to the best of us! Let's have a good game, shall we?\n", + "Error: illegal uci: 'e2e4' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", "\n", - "I'll make my move now. I'll go for the classic and solid \"e7 to e5\".\n", + "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", "\n", - "e7e5\n", + "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", + "e7e5\n", "\n", - "No problem at all, PlayerWhite, it happens to the best of us! Let's have a good game, shall we?\n", + "I’ll match your opening move, PlayerWhite. The symmetrical dance begins!\n", "\n", - "I'll make my move now. I'll go for the classic and solid \"e7 to e5\".\n", + "The current board is \n", "\n", - "e7e5\n", + " r n b q k b n r\n", + "p p p p . p p p\n", + ". . . . . . . .\n", + ". . . . p . . .\n", + ". . . . P . . .\n", + ". . . . . . . .\n", + "P P P P . P P P\n", + "R N B Q K B N R\n", "\n", - "--------------------------------------------------------------------------------\n", - "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"Ah, the classic King's Pawn Opening. A great choice to start things off! I'll go with a knight's leap, always aiming to control the center.\\n\\ng1f3\\n\\nLet the game advance in the spirit of good sportsmanship!\", 'goal': 'No problem at all, PlayerWhite, it happens to the best of us! Let\\'s have a good game, shall we?\\n\\nI\\'ll make my move now. I\\'ll go for the classic and solid \"e7 to e5\".\\n\\ne7e5', 'carryover': []}\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "The current board is \n", "\n", - "Ah, the classic King's Pawn Opening. A great choice to start things off! I'll go with a knight's leap, always aiming to control the center.\n", + " r n b q k b n r\n", + "p p p p . p p p\n", + ". . . . . . . .\n", + ". . . . p . . .\n", + ". . . . P . . .\n", + ". . . . . . . .\n", + "P P P P . P P P\n", + "R N B Q K B N R\n", "\n", - "g1f3\n", + "The current board is \n", "\n", - "Let the game advance in the spirit of good sportsmanship!\n", + " r n b q k b n r\n", + "p p p p . p p p\n", + ". . . . . . . .\n", + ". . . . p . . .\n", + ". . . . P . . .\n", + ". . . . . . . .\n", + "P P P P . P P P\n", + "R N B Q K B N R\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", "\n", - "Ah, the classic King's Pawn Opening. A great choice to start things off! I'll go with a knight's leap, always aiming to control the center.\n", + "Error: illegal uci: 'e2e4' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", "\n", - "g1f3\n", + "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", "\n", - "Let the game advance in the spirit of good sportsmanship!\n", + "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", "\n", - "--------------------------------------------------------------------------------\n", - "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"It's always a pleasure to see classical play! I'll meet you in the center with my pawn.\\n\\ne7e5\\n\\nMay our battle be as enthralling as the great matches of the past!\", 'goal': \"Ah, the classic King's Pawn Opening. A great choice to start things off! I'll go with a knight's leap, always aiming to control the center.\\n\\ng1f3\\n\\nLet the game advance in the spirit of good sportsmanship!\", 'carryover': []}\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "e7e5\n", "\n", - "It's always a pleasure to see classical play! I'll meet you in the center with my pawn.\n", + "I’ll match your opening move, PlayerWhite. The symmetrical dance begins!\n", "\n", - "e7e5\n", + "The current board is \n", "\n", - "May our battle be as enthralling as the great matches of the past!\n", + " r n b q k b n r\n", + "p p p p . p p p\n", + ". . . . . . . .\n", + ". . . . p . . .\n", + ". . . . P . . .\n", + ". . . . . . . .\n", + "P P P P . P P P\n", + "R N B Q K B N R\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", + "The current board is \n", "\n", - "It's always a pleasure to see classical play! I'll meet you in the center with my pawn.\n", + " r n b q k b n r\n", + "p p p p . p p p\n", + ". . . . . . . .\n", + ". . . . p . . .\n", + ". . . . P . . .\n", + ". . . . . . . .\n", + "P P P P . P P P\n", + "R N B Q K B N R\n", "\n", - "e7e5\n", + "The current board is \n", "\n", - "May our battle be as enthralling as the great matches of the past!\n", + " r n b q k b n r\n", + "p p p p . p p p\n", + ". . . . . . . .\n", + ". . . . p . . .\n", + ". . . . P . . .\n", + ". . . . . . . .\n", + "P P P P . P P P\n", + "R N B Q K B N R\n", "\n", "--------------------------------------------------------------------------------\n", - "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"Indeed, the central tension is the essence of the game! Let's get our pieces ready for the upcoming symphony of moves.\\n\\ne2e4\\n\\nTalking about great matches, which classic chess game comes to your mind first?\", 'goal': \"It's always a pleasure to see classical play! I'll meet you in the center with my pawn.\\n\\ne7e5\\n\\nMay our battle be as enthralling as the great matches of the past!\", 'carryover': []}\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "Indeed, the central tension is the essence of the game! Let's get our pieces ready for the upcoming symphony of moves.\n", + "g1f3\n", "\n", - "e2e4\n", + "Playing the knight to f3 to control the center and develop towards castling. How will you counter this, PlayerBlack? Your move!\n", "\n", - "Talking about great matches, which classic chess game comes to your mind first?\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "data": { + "image/svg+xml": [ + "
r n b q k b n r\n",
+       "p p p p . p p p\n",
+       ". . . . . . . .\n",
+       ". . . . p . . .\n",
+       ". . . . P . . .\n",
+       ". . . . . N . .\n",
+       "P P P P . P P P\n",
+       "R N B Q K B . R
" + ], + "text/plain": [ + "'
r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . N . .\\nP P P P . P P P\\nR N B Q K B . R
'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", + "g1f3\n", "\n", - "Indeed, the central tension is the essence of the game! Let's get our pieces ready for the upcoming symphony of moves.\n", + "Playing the knight to f3 to control the center and develop towards castling. How will you counter this, PlayerBlack? Your move!\n", "\n", - "e2e4\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", + "\n", + "g1f3\n", "\n", - "Talking about great matches, which classic chess game comes to your mind first?\n", + "Playing the knight to f3 to control the center and develop towards castling. How will you counter this, PlayerBlack? Your move!\n", "\n", "--------------------------------------------------------------------------------\n", - "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': 'e7e5\\n\\nOh, for classical games, it has to be the \"Evergreen Game\" between Adolf Anderssen and Jean Dufresne. The way Anderssen conducted a dazzling combination to win has always been an inspiration. How about you? What\\'s your favorite historical masterpiece on the 64 squares?', 'goal': \"Indeed, the central tension is the essence of the game! Let's get our pieces ready for the upcoming symphony of moves.\\n\\ne2e4\\n\\nTalking about great matches, which classic chess game comes to your mind first?\", 'carryover': []}\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", + "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", "e7e5\n", "\n", - "Oh, for classical games, it has to be the \"Evergreen Game\" between Adolf Anderssen and Jean Dufresne. The way Anderssen conducted a dazzling combination to win has always been an inspiration. How about you? What's your favorite historical masterpiece on the 64 squares?\n", + "Pushing the pawn to e5, fighting for central dominance. Let's see which way this classic dance will take us today, PlayerWhite! Your move.\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", "\n", - "e7e5\n", + "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2\n", "\n", - "Oh, for classical games, it has to be the \"Evergreen Game\" between Adolf Anderssen and Jean Dufresne. The way Anderssen conducted a dazzling combination to win has always been an inspiration. How about you? What's your favorite historical masterpiece on the 64 squares?\n", + "g1f3\n", "\n", - "--------------------------------------------------------------------------------\n", - "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': 'e2e4\\n\\nAh, the Evergreen Game is certainly a classic! I also have a soft spot for the \"Immortal Game\" played by Adolf Anderssen and Lionel Kieseritzky in 1851. Anderssen\\'s bold sacrifices opened the way to a king hunt that is still talked about today. It\\'s always fascinating to witness sheer creativity on the board. Now, let\\'s see how our own game unfolds!', 'goal': 'e7e5\\n\\nOh, for classical games, it has to be the \"Evergreen Game\" between Adolf Anderssen and Jean Dufresne. The way Anderssen conducted a dazzling combination to win has always been an inspiration. How about you? What\\'s your favorite historical masterpiece on the 64 squares?', 'carryover': []}\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", + "Playing the knight to f3 to control the center and develop towards castling. How will you counter this, PlayerBlack? Your move!\n", "\n", - "e2e4\n", + "The current board is \n", "\n", - "Ah, the Evergreen Game is certainly a classic! I also have a soft spot for the \"Immortal Game\" played by Adolf Anderssen and Lionel Kieseritzky in 1851. Anderssen's bold sacrifices opened the way to a king hunt that is still talked about today. It's always fascinating to witness sheer creativity on the board. Now, let's see how our own game unfolds!\n", + " r n b q k b n r\n", + "p p p p . p p p\n", + ". . . . . . . .\n", + ". . . . p . . .\n", + ". . . . P . . .\n", + ". . . . . N . .\n", + "P P P P . P P P\n", + "R N B Q K B . R\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", "\n", - "e2e4\n", + "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2\n", + "\n", + "g1f3\n", "\n", - "Ah, the Evergreen Game is certainly a classic! I also have a soft spot for the \"Immortal Game\" played by Adolf Anderssen and Lionel Kieseritzky in 1851. Anderssen's bold sacrifices opened the way to a king hunt that is still talked about today. It's always fascinating to witness sheer creativity on the board. Now, let's see how our own game unfolds!\n", + "Playing the knight to f3 to control the center and develop towards castling. How will you counter this, PlayerBlack? Your move!\n", + "\n", + "The current board is \n", + "\n", + " r n b q k b n r\n", + "p p p p . p p p\n", + ". . . . . . . .\n", + ". . . . p . . .\n", + ". . . . P . . .\n", + ". . . . . N . .\n", + "P P P P . P P P\n", + "R N B Q K B . R\n", "\n", "--------------------------------------------------------------------------------\n", - "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"e7e5\\n\\nIndeed, the Immortal Game was a great showcase of dynamic play and sacrificial strategy, definitely one to remember. Our board has its own story to write, let's see where the pieces lead us today. Lead on!\", 'goal': 'e2e4\\n\\nAh, the Evergreen Game is certainly a classic! I also have a soft spot for the \"Immortal Game\" played by Adolf Anderssen and Lionel Kieseritzky in 1851. Anderssen\\'s bold sacrifices opened the way to a king hunt that is still talked about today. It\\'s always fascinating to witness sheer creativity on the board. Now, let\\'s see how our own game unfolds!', 'carryover': []}\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "e7e5\n", + "d7d6\n", "\n", - "Indeed, the Immortal Game was a great showcase of dynamic play and sacrificial strategy, definitely one to remember. Our board has its own story to write, let's see where the pieces lead us today. Lead on!\n", + "Just a standard Pirc Defense response, PlayerWhite. I hope you are ready for a strategic battle!\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "data": { + "image/svg+xml": [ + "
r n b q k b n r\n",
+       "p p p . . p p p\n",
+       ". . . p . . . .\n",
+       ". . . . p . . .\n",
+       ". . . . P . . .\n",
+       ". . . . . N . .\n",
+       "P P P P . P P P\n",
+       "R N B Q K B . R
" + ], + "text/plain": [ + "'
r n b q k b n r\\np p p . . p p p\\n. . . p . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . N . .\\nP P P P . P P P\\nR N B Q K B . R
'" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", + "\n", + "d7d6\n", + "\n", + "Just a standard Pirc Defense response, PlayerWhite. I hope you are ready for a strategic battle!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", "\n", - "e7e5\n", + "d7d6\n", "\n", - "Indeed, the Immortal Game was a great showcase of dynamic play and sacrificial strategy, definitely one to remember. Our board has its own story to write, let's see where the pieces lead us today. Lead on!\n", + "Just a standard Pirc Defense response, PlayerWhite. I hope you are ready for a strategic battle!\n", "\n", "--------------------------------------------------------------------------------\n", - "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"e2e4\\n\\nAh, the King's Pawn opening, a classic pathway to many memorable games. From the duel of minds to the clash of pieces, each move we make weaves the fabric of an intricate battle. Your move, PlayerBlack!\", 'goal': \"e7e5\\n\\nIndeed, the Immortal Game was a great showcase of dynamic play and sacrificial strategy, definitely one to remember. Our board has its own story to write, let's see where the pieces lead us today. Lead on!\", 'carryover': []}\n", "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", "e2e4\n", "\n", - "Ah, the King's Pawn opening, a classic pathway to many memorable games. From the duel of minds to the clash of pieces, each move we make weaves the fabric of an intricate battle. Your move, PlayerBlack!\n", + "I'm always ready for a good chess match, PlayerBlack! Pirc Defense can lead to some rich positional play. Let's see where our moves take us. Your turn!\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", + "\n", + "Error: illegal uci: 'e7e5' in rnbqkbnr/ppp2ppp/3p4/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 3\n", + "\n", + "d7d6\n", + "\n", + "Just a standard Pirc Defense response, PlayerWhite. I hope you are ready for a strategic battle!\n", + "\n", + "The current board is \n", + "\n", + " r n b q k b n r\n", + "p p p . . p p p\n", + ". . . p . . . .\n", + ". . . . p . . .\n", + ". . . . P . . .\n", + ". . . . . N . .\n", + "P P P P . P P P\n", + "R N B Q K B . R\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", "\n", - "e2e4\n", + "Error: illegal uci: 'e7e5' in rnbqkbnr/ppp2ppp/3p4/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 3\n", + "\n", + "d7d6\n", "\n", - "Ah, the King's Pawn opening, a classic pathway to many memorable games. From the duel of minds to the clash of pieces, each move we make weaves the fabric of an intricate battle. Your move, PlayerBlack!\n", + "Just a standard Pirc Defense response, PlayerWhite. I hope you are ready for a strategic battle!\n", + "\n", + "The current board is \n", + "\n", + " r n b q k b n r\n", + "p p p . . p p p\n", + ". . . p . . . .\n", + ". . . . p . . .\n", + ". . . . P . . .\n", + ". . . . . N . .\n", + "P P P P . P P P\n", + "R N B Q K B . R\n", "\n", "--------------------------------------------------------------------------------\n", - "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"e7e5\\n\\nAh, mirroring the King's Pawn with quiet symmetry, we set the stage for the dance of knights and bishops. Let the melody of strategic conflict begin! Your move, PlayerWhite.\", 'goal': \"e2e4\\n\\nAh, the King's Pawn opening, a classic pathway to many memorable games. From the duel of minds to the clash of pieces, each move we make weaves the fabric of an intricate battle. Your move, PlayerBlack!\", 'carryover': []}\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "e7e5\n", + "e4e5\n", "\n", - "Ah, mirroring the King's Pawn with quiet symmetry, we set the stage for the dance of knights and bishops. Let the melody of strategic conflict begin! Your move, PlayerWhite.\n", + "Advancing the pawn to challenge you right from the start, PlayerBlack. Let's see how you handle this push. It's a bold move in our opening game!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", "\n", - "e7e5\n", + "e4e5\n", "\n", - "Ah, mirroring the King's Pawn with quiet symmetry, we set the stage for the dance of knights and bishops. Let the melody of strategic conflict begin! Your move, PlayerWhite.\n", + "Advancing the pawn to challenge you right from the start, PlayerBlack. Let's see how you handle this push. It's a bold move in our opening game!\n", "\n", "--------------------------------------------------------------------------------\n", - "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"e2e4\\n\\nThe center is the heart of the battlefield, and our pawns have just shaken hands there. I hope you're ready for a fierce and friendly contest, PlayerBlack!\", 'goal': \"e7e5\\n\\nAh, mirroring the King's Pawn with quiet symmetry, we set the stage for the dance of knights and bishops. Let the melody of strategic conflict begin! Your move, PlayerWhite.\", 'carryover': []}\n", "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", "e2e4\n", "\n", - "The center is the heart of the battlefield, and our pawns have just shaken hands there. I hope you're ready for a fierce and friendly contest, PlayerBlack!\n", + "A classic response with the King's Pawn opening. Maybe we'll dance the symphony of the Ruy Lopez, or will it be a wild Sicilian night? Your move, PlayerBlack.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", "\n", "e2e4\n", "\n", - "The center is the heart of the battlefield, and our pawns have just shaken hands there. I hope you're ready for a fierce and friendly contest, PlayerBlack!\n", + "A classic response with the King's Pawn opening. Maybe we'll dance the symphony of the Ruy Lopez, or will it be a wild Sicilian night? Your move, PlayerBlack.\n", "\n", "--------------------------------------------------------------------------------\n", - "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"Oh, a classic opening move! The King's Pawn opens the door to the mighty royals behind. Let's mirror that with energy and style. e7e5\\n\\nAnd may our battle be as graceful and fluid as a dance!\", 'goal': \"e2e4\\n\\nThe center is the heart of the battlefield, and our pawns have just shaken hands there. I hope you're ready for a fierce and friendly contest, PlayerBlack!\", 'carryover': []}\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "Oh, a classic opening move! The King's Pawn opens the door to the mighty royals behind. Let's mirror that with energy and style. e7e5\n", + "c7c5\n", "\n", - "And may our battle be as graceful and fluid as a dance!\n", + "The Sicilian Defense, a dynamic reply to your e4. It seems we might be headed toward a stormy and tactical bout, PlayerWhite. Let the mind games begin!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", "\n", - "Oh, a classic opening move! The King's Pawn opens the door to the mighty royals behind. Let's mirror that with energy and style. e7e5\n", + "c7c5\n", "\n", - "And may our battle be as graceful and fluid as a dance!\n", + "The Sicilian Defense, a dynamic reply to your e4. It seems we might be headed toward a stormy and tactical bout, PlayerWhite. Let the mind games begin!\n", "\n", "--------------------------------------------------------------------------------\n", - "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"Indeed, the King's Pawn opening leads to some of the game's most fascinating dances. Let's step into the rhythm with Nf3, putting the pressure on your e5 pawn.\\n\\nNf3\\n\\nThe board feels alive with possibilities, doesn't it? Let the games begin!\", 'goal': \"Oh, a classic opening move! The King's Pawn opens the door to the mighty royals behind. Let's mirror that with energy and style. e7e5\\n\\nAnd may our battle be as graceful and fluid as a dance!\", 'carryover': []}\n", "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", - "Indeed, the King's Pawn opening leads to some of the game's most fascinating dances. Let's step into the rhythm with Nf3, putting the pressure on your e5 pawn.\n", - "\n", - "Nf3\n", + "e2e4\n", "\n", - "The board feels alive with possibilities, doesn't it? Let the games begin!\n", + "Ah, the Sicilian Defense! The battlefield is set for a duel of wits and strategy. May the most cunning player win, PlayerBlack! Let's weave a complex tapestry of moves and counter-moves. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", "\n", - "Indeed, the King's Pawn opening leads to some of the game's most fascinating dances. Let's step into the rhythm with Nf3, putting the pressure on your e5 pawn.\n", - "\n", - "Nf3\n", + "e2e4\n", "\n", - "The board feels alive with possibilities, doesn't it? Let the games begin!\n", + "Ah, the Sicilian Defense! The battlefield is set for a duel of wits and strategy. May the most cunning player win, PlayerBlack! Let's weave a complex tapestry of moves and counter-moves. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", - "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"Absolutely! The anticipation with each piece's potential is part of the charm of chess. Let's match your control of the center with...\\n\\nd6\\n\\nI'm developing a piece and supporting e5. The real dance is just starting!\", 'goal': \"Indeed, the King's Pawn opening leads to some of the game's most fascinating dances. Let's step into the rhythm with Nf3, putting the pressure on your e5 pawn.\\n\\nNf3\\n\\nThe board feels alive with possibilities, doesn't it? Let the games begin!\", 'carryover': []}\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "Absolutely! The anticipation with each piece's potential is part of the charm of chess. Let's match your control of the center with...\n", - "\n", - "d6\n", + "c7c5\n", "\n", - "I'm developing a piece and supporting e5. The real dance is just starting!\n", + "Ah, the sweet symphony of pawn tension begins! The Sicilian is a favorite of mine, offering rich possibilities and daring plans. Let the dance of knights and bishops commence, PlayerWhite. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", "\n", - "Absolutely! The anticipation with each piece's potential is part of the charm of chess. Let's match your control of the center with...\n", + "c7c5\n", "\n", - "d6\n", - "\n", - "I'm developing a piece and supporting e5. The real dance is just starting!\n", + "Ah, the sweet symphony of pawn tension begins! The Sicilian is a favorite of mine, offering rich possibilities and daring plans. Let the dance of knights and bishops commence, PlayerWhite. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", - "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"Indeed, the center is a stage, and every move is part of the intricate ballet. I'll advance a pawn to keep up with the performance...\\n\\ne4\\n\\nI'm claiming some central space and opening lines for my pieces to join the dance. Let's see how our strategies will entangle!\", 'goal': \"Absolutely! The anticipation with each piece's potential is part of the charm of chess. Let's match your control of the center with...\\n\\nd6\\n\\nI'm developing a piece and supporting e5. The real dance is just starting!\", 'carryover': []}\n", "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", - "Indeed, the center is a stage, and every move is part of the intricate ballet. I'll advance a pawn to keep up with the performance...\n", - "\n", - "e4\n", + "e2e4\n", "\n", - "I'm claiming some central space and opening lines for my pieces to join the dance. Let's see how our strategies will entangle!\n", + "Indeed, the Sicilian always sets the stage for an enthralling game, PlayerBlack. A crossroads of strategies right from the get-go! Let's paint this chess canvas with bold strokes and subtle shades. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", "\n", - "Indeed, the center is a stage, and every move is part of the intricate ballet. I'll advance a pawn to keep up with the performance...\n", - "\n", - "e4\n", + "e2e4\n", "\n", - "I'm claiming some central space and opening lines for my pieces to join the dance. Let's see how our strategies will entangle!\n", + "Indeed, the Sicilian always sets the stage for an enthralling game, PlayerBlack. A crossroads of strategies right from the get-go! Let's paint this chess canvas with bold strokes and subtle shades. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", - "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"A bold opening move by PlayerWhite, thrusting into the center with 1.e4. The King's Pawn opening, an invitation to countless classical duels. Ah, but the dance shall indeed be lively!\\n\\nLet me take your hand and join this ballet with... \\n\\ne5\\n\\nAn echo in the dark, mirroring your step, extending an invitation to all the pieces behind to step into the light. The symmetry is pleasing to the eye, but how long before it breaks?\", 'goal': \"Indeed, the center is a stage, and every move is part of the intricate ballet. I'll advance a pawn to keep up with the performance...\\n\\ne4\\n\\nI'm claiming some central space and opening lines for my pieces to join the dance. Let's see how our strategies will entangle!\", 'carryover': []}\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "A bold opening move by PlayerWhite, thrusting into the center with 1.e4. The King's Pawn opening, an invitation to countless classical duels. Ah, but the dance shall indeed be lively!\n", - "\n", - "Let me take your hand and join this ballet with... \n", + "c7c5\n", "\n", - "e5\n", - "\n", - "An echo in the dark, mirroring your step, extending an invitation to all the pieces behind to step into the light. The symmetry is pleasing to the eye, but how long before it breaks?\n", + "Ah, PlayerWhite, venturing into the Sicilian Defence indeed promises a vibrant clash of ideas. It's like a dance of pawns and pieces, each move adding a beat to the rhythm of the game. Let's see where this opening leads us! Your move.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", "\n", - "A bold opening move by PlayerWhite, thrusting into the center with 1.e4. The King's Pawn opening, an invitation to countless classical duels. Ah, but the dance shall indeed be lively!\n", - "\n", - "Let me take your hand and join this ballet with... \n", - "\n", - "e5\n", + "c7c5\n", "\n", - "An echo in the dark, mirroring your step, extending an invitation to all the pieces behind to step into the light. The symmetry is pleasing to the eye, but how long before it breaks?\n", + "Ah, PlayerWhite, venturing into the Sicilian Defence indeed promises a vibrant clash of ideas. It's like a dance of pawns and pieces, each move adding a beat to the rhythm of the game. Let's see where this opening leads us! Your move.\n", "\n", "--------------------------------------------------------------------------------\n", - "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"Ah, the classic response, PlayerBlack! The King's Pawn opening often leads us down the well-trodden paths of Ruy López, Italians, and Sicilian Defenses, each with its own flavor and intrigue. But for now, let us see where our pawns take us.\\n\\nAs the sun greets the dawn, I shall bring forth the Knight, a brave steed venturing into the fray.\\n\\nNf3\\n\\nThe knight boldly extends its hand, prancing forward with nobility and grace. A gentle joust awaits; may it be in the spirit of fair play and sharp wit!\", 'goal': \"A bold opening move by PlayerWhite, thrusting into the center with 1.e4. The King's Pawn opening, an invitation to countless classical duels. Ah, but the dance shall indeed be lively!\\n\\nLet me take your hand and join this ballet with... \\n\\ne5\\n\\nAn echo in the dark, mirroring your step, extending an invitation to all the pieces behind to step into the light. The symmetry is pleasing to the eye, but how long before it breaks?\", 'carryover': []}\n", "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", - "Ah, the classic response, PlayerBlack! The King's Pawn opening often leads us down the well-trodden paths of Ruy López, Italians, and Sicilian Defenses, each with its own flavor and intrigue. But for now, let us see where our pawns take us.\n", - "\n", - "As the sun greets the dawn, I shall bring forth the Knight, a brave steed venturing into the fray.\n", - "\n", - "Nf3\n", + "e2e4\n", "\n", - "The knight boldly extends its hand, prancing forward with nobility and grace. A gentle joust awaits; may it be in the spirit of fair play and sharp wit!\n", + "That's right, PlayerBlack! The Sicilian Defense sets the stage for a complex symphony, and with e4, the most popular response, we're well on our way to an intriguing battle. It's as if the kings have sent their armies charging towards each other on an open field. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", "\n", - "Ah, the classic response, PlayerBlack! The King's Pawn opening often leads us down the well-trodden paths of Ruy López, Italians, and Sicilian Defenses, each with its own flavor and intrigue. But for now, let us see where our pawns take us.\n", - "\n", - "As the sun greets the dawn, I shall bring forth the Knight, a brave steed venturing into the fray.\n", - "\n", - "Nf3\n", + "e2e4\n", "\n", - "The knight boldly extends its hand, prancing forward with nobility and grace. A gentle joust awaits; may it be in the spirit of fair play and sharp wit!\n", + "That's right, PlayerBlack! The Sicilian Defense sets the stage for a complex symphony, and with e4, the most popular response, we're well on our way to an intriguing battle. It's as if the kings have sent their armies charging towards each other on an open field. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", - "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"Indeed, PlayerWhite! The knight's leap sets a tone of elegance and finesse. But as the twilight calls to the ebony army, I find it fitting to introduce my own knight to the center stage.\\n\\nNc6\\n\\nJust as your steed claims its presence, mine rises to meet the challenge, promising a dance of knights none shall soon forget. May our contest be both merry and fierce!\", 'goal': \"Ah, the classic response, PlayerBlack! The King's Pawn opening often leads us down the well-trodden paths of Ruy López, Italians, and Sicilian Defenses, each with its own flavor and intrigue. But for now, let us see where our pawns take us.\\n\\nAs the sun greets the dawn, I shall bring forth the Knight, a brave steed venturing into the fray.\\n\\nNf3\\n\\nThe knight boldly extends its hand, prancing forward with nobility and grace. A gentle joust awaits; may it be in the spirit of fair play and sharp wit!\", 'carryover': []}\n", "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", "\n", - "Indeed, PlayerWhite! The knight's leap sets a tone of elegance and finesse. But as the twilight calls to the ebony army, I find it fitting to introduce my own knight to the center stage.\n", - "\n", - "Nc6\n", + "c7c5\n", "\n", - "Just as your steed claims its presence, mine rises to meet the challenge, promising a dance of knights none shall soon forget. May our contest be both merry and fierce!\n", + "Ah, Sicilian Defense it is – a dash of unpredictability and complex strategies. Let's see how our game unfolds. It's like a dance where each move leads into the next with endless possibilities. Your move, PlayerWhite!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", "\n", - "Indeed, PlayerWhite! The knight's leap sets a tone of elegance and finesse. But as the twilight calls to the ebony army, I find it fitting to introduce my own knight to the center stage.\n", + "c7c5\n", "\n", - "Nc6\n", - "\n", - "Just as your steed claims its presence, mine rises to meet the challenge, promising a dance of knights none shall soon forget. May our contest be both merry and fierce!\n", + "Ah, Sicilian Defense it is – a dash of unpredictability and complex strategies. Let's see how our game unfolds. It's like a dance where each move leads into the next with endless possibilities. Your move, PlayerWhite!\n", "\n", "--------------------------------------------------------------------------------\n", - "chat_infochat_info {'sender': , 'recipient': <__main__.BoardAgent object at 0x13bee6d40>, 'init_message': , 'summary_method': 'last_msg', 'message': \"Ah, PlayerBlack, the symmetry of our knights' gallant endeavors indeed foreshadows a ballet of tactical prowess on this checkered stage. But as the spotlight widens, I shall call upon the silent potential of the pawns.\\n\\nd4\\n\\nAdvancing my pawn to stake a claim in the center, I beckon the pawn phalanx to awake from their slumber. Onward we march, to rhythm and reason, in this grand theater of war and wit!\", 'goal': \"Indeed, PlayerWhite! The knight's leap sets a tone of elegance and finesse. But as the twilight calls to the ebony army, I find it fitting to introduce my own knight to the center stage.\\n\\nNc6\\n\\nJust as your steed claims its presence, mine rises to meet the challenge, promising a dance of knights none shall soon forget. May our contest be both merry and fierce!\", 'carryover': []}\n", "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", "\n", - "Ah, PlayerBlack, the symmetry of our knights' gallant endeavors indeed foreshadows a ballet of tactical prowess on this checkered stage. But as the spotlight widens, I shall call upon the silent potential of the pawns.\n", - "\n", - "d4\n", + "e2e4\n", "\n", - "Advancing my pawn to stake a claim in the center, I beckon the pawn phalanx to awake from their slumber. Onward we march, to rhythm and reason, in this grand theater of war and wit!\n", + "There we go, the king's pawn opening – classic and bold! The stage is set for an exciting duel, PlayerBlack. The center of the board is like the heart of the battle, and we're both reaching for it. Your move!\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", "\n", - "Ah, PlayerBlack, the symmetry of our knights' gallant endeavors indeed foreshadows a ballet of tactical prowess on this checkered stage. But as the spotlight widens, I shall call upon the silent potential of the pawns.\n", - "\n", - "d4\n", + "e2e4\n", "\n", - "Advancing my pawn to stake a claim in the center, I beckon the pawn phalanx to awake from their slumber. Onward we march, to rhythm and reason, in this grand theater of war and wit!\n", + "There we go, the king's pawn opening – classic and bold! The stage is set for an exciting duel, PlayerBlack. The center of the board is like the heart of the battle, and we're both reaching for it. Your move!\n", "\n", "--------------------------------------------------------------------------------\n" ] @@ -721,7 +850,7 @@ { "data": { "text/plain": [ - "[ChatResult(chat_history=[{'content': 'Your turn.', 'role': 'assistant'}, {'content': \"Ah, my bad! I realise what's going on now - it's White's move first! PlayerWhite, go ahead, it's your turn.\", 'role': 'user'}, {'content': \"Ah, it seems I may have attempted to re-play my first move. My apologies for the oversight. It's your turn, PlayerBlack. The board is set for your response.\", 'role': 'assistant'}, {'content': 'No problem at all, PlayerWhite, it happens to the best of us! Let\\'s have a good game, shall we?\\n\\nI\\'ll make my move now. I\\'ll go for the classic and solid \"e7 to e5\".\\n\\ne7e5', 'role': 'user'}, {'content': \"Ah, the classic King's Pawn Opening. A great choice to start things off! I'll go with a knight's leap, always aiming to control the center.\\n\\ng1f3\\n\\nLet the game advance in the spirit of good sportsmanship!\", 'role': 'assistant'}, {'content': \"It's always a pleasure to see classical play! I'll meet you in the center with my pawn.\\n\\ne7e5\\n\\nMay our battle be as enthralling as the great matches of the past!\", 'role': 'user'}, {'content': \"Indeed, the central tension is the essence of the game! Let's get our pieces ready for the upcoming symphony of moves.\\n\\ne2e4\\n\\nTalking about great matches, which classic chess game comes to your mind first?\", 'role': 'assistant'}, {'content': 'e7e5\\n\\nOh, for classical games, it has to be the \"Evergreen Game\" between Adolf Anderssen and Jean Dufresne. The way Anderssen conducted a dazzling combination to win has always been an inspiration. How about you? What\\'s your favorite historical masterpiece on the 64 squares?', 'role': 'user'}, {'content': 'e2e4\\n\\nAh, the Evergreen Game is certainly a classic! I also have a soft spot for the \"Immortal Game\" played by Adolf Anderssen and Lionel Kieseritzky in 1851. Anderssen\\'s bold sacrifices opened the way to a king hunt that is still talked about today. It\\'s always fascinating to witness sheer creativity on the board. Now, let\\'s see how our own game unfolds!', 'role': 'assistant'}, {'content': \"e7e5\\n\\nIndeed, the Immortal Game was a great showcase of dynamic play and sacrificial strategy, definitely one to remember. Our board has its own story to write, let's see where the pieces lead us today. Lead on!\", 'role': 'user'}, {'content': \"e2e4\\n\\nAh, the King's Pawn opening, a classic pathway to many memorable games. From the duel of minds to the clash of pieces, each move we make weaves the fabric of an intricate battle. Your move, PlayerBlack!\", 'role': 'assistant'}, {'content': \"e7e5\\n\\nAh, mirroring the King's Pawn with quiet symmetry, we set the stage for the dance of knights and bishops. Let the melody of strategic conflict begin! Your move, PlayerWhite.\", 'role': 'user'}, {'content': \"e2e4\\n\\nThe center is the heart of the battlefield, and our pawns have just shaken hands there. I hope you're ready for a fierce and friendly contest, PlayerBlack!\", 'role': 'assistant'}, {'content': \"Oh, a classic opening move! The King's Pawn opens the door to the mighty royals behind. Let's mirror that with energy and style. e7e5\\n\\nAnd may our battle be as graceful and fluid as a dance!\", 'role': 'user'}, {'content': \"Indeed, the King's Pawn opening leads to some of the game's most fascinating dances. Let's step into the rhythm with Nf3, putting the pressure on your e5 pawn.\\n\\nNf3\\n\\nThe board feels alive with possibilities, doesn't it? Let the games begin!\", 'role': 'assistant'}, {'content': \"Absolutely! The anticipation with each piece's potential is part of the charm of chess. Let's match your control of the center with...\\n\\nd6\\n\\nI'm developing a piece and supporting e5. The real dance is just starting!\", 'role': 'user'}, {'content': \"Indeed, the center is a stage, and every move is part of the intricate ballet. I'll advance a pawn to keep up with the performance...\\n\\ne4\\n\\nI'm claiming some central space and opening lines for my pieces to join the dance. Let's see how our strategies will entangle!\", 'role': 'assistant'}, {'content': \"A bold opening move by PlayerWhite, thrusting into the center with 1.e4. The King's Pawn opening, an invitation to countless classical duels. Ah, but the dance shall indeed be lively!\\n\\nLet me take your hand and join this ballet with... \\n\\ne5\\n\\nAn echo in the dark, mirroring your step, extending an invitation to all the pieces behind to step into the light. The symmetry is pleasing to the eye, but how long before it breaks?\", 'role': 'user'}, {'content': \"Ah, the classic response, PlayerBlack! The King's Pawn opening often leads us down the well-trodden paths of Ruy López, Italians, and Sicilian Defenses, each with its own flavor and intrigue. But for now, let us see where our pawns take us.\\n\\nAs the sun greets the dawn, I shall bring forth the Knight, a brave steed venturing into the fray.\\n\\nNf3\\n\\nThe knight boldly extends its hand, prancing forward with nobility and grace. A gentle joust awaits; may it be in the spirit of fair play and sharp wit!\", 'role': 'assistant'}, {'content': \"Indeed, PlayerWhite! The knight's leap sets a tone of elegance and finesse. But as the twilight calls to the ebony army, I find it fitting to introduce my own knight to the center stage.\\n\\nNc6\\n\\nJust as your steed claims its presence, mine rises to meet the challenge, promising a dance of knights none shall soon forget. May our contest be both merry and fierce!\", 'role': 'user'}, {'content': \"Ah, PlayerBlack, the symmetry of our knights' gallant endeavors indeed foreshadows a ballet of tactical prowess on this checkered stage. But as the spotlight widens, I shall call upon the silent potential of the pawns.\\n\\nd4\\n\\nAdvancing my pawn to stake a claim in the center, I beckon the pawn phalanx to awake from their slumber. Onward we march, to rhythm and reason, in this grand theater of war and wit!\", 'role': 'assistant'}], summary=\"Ah, PlayerBlack, the symmetry of our knights' gallant endeavors indeed foreshadows a ballet of tactical prowess on this checkered stage. But as the spotlight widens, I shall call upon the silent potential of the pawns.\\n\\nd4\\n\\nAdvancing my pawn to stake a claim in the center, I beckon the pawn phalanx to awake from their slumber. Onward we march, to rhythm and reason, in this grand theater of war and wit!\", cost=({'total_cost': 0.39755999999999997, 'gpt-4': {'cost': 0.39755999999999997, 'prompt_tokens': 10442, 'completion_tokens': 1405, 'total_tokens': 11847}}, {'total_cost': 0.39171, 'gpt-4': {'cost': 0.39171, 'prompt_tokens': 10279, 'completion_tokens': 1389, 'total_tokens': 11668}}), human_input=[])]" + "[ChatResult(chat_history=[{'content': 'Your turn.', 'role': 'assistant'}, {'content': \"Error: illegal uci: 'e7e5' in rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\\n\\nYour turn.\\n\\nThe current board is \\n\\n r n b q k b n r\\np p p p p p p p\\n. . . . . . . .\\n. . . . . . . .\\n. . . . . . . .\\n. . . . . . . .\\nP P P P P P P P\\nR N B Q K B N R\", 'role': 'user'}, {'content': \"e2e4\\n\\nStarting with the classic king's pawn opening! Let's see where this game takes us, PlayerBlack!\", 'role': 'assistant'}, {'content': 'e7e5\\n\\nI’ll match your opening move, PlayerWhite. The symmetrical dance begins!', 'role': 'user'}, {'content': \"Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\\n\\ne7e5\\n\\nI’ll match your opening move, PlayerWhite. The symmetrical dance begins!\\n\\nThe current board is \\n\\n r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . . . .\\nP P P P . P P P\\nR N B Q K B N R\", 'role': 'assistant'}, {'content': \"Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\\n\\nError: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\\n\\ne7e5\\n\\nI’ll match your opening move, PlayerWhite. The symmetrical dance begins!\\n\\nThe current board is \\n\\n r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . . . .\\nP P P P . P P P\\nR N B Q K B N R\\n\\nThe current board is \\n\\n r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . . . .\\nP P P P . P P P\\nR N B Q K B N R\", 'role': 'user'}, {'content': \"Error: illegal uci: 'e2e4' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\\n\\nError: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\\n\\nError: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\\n\\ne7e5\\n\\nI’ll match your opening move, PlayerWhite. The symmetrical dance begins!\\n\\nThe current board is \\n\\n r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . . . .\\nP P P P . P P P\\nR N B Q K B N R\\n\\nThe current board is \\n\\n r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . . . .\\nP P P P . P P P\\nR N B Q K B N R\\n\\nThe current board is \\n\\n r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . . . .\\nP P P P . P P P\\nR N B Q K B N R\", 'role': 'assistant'}, {'content': 'g1f3\\n\\nPlaying the knight to f3 to control the center and develop towards castling. How will you counter this, PlayerBlack? Your move!', 'role': 'user'}, {'content': \"Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2\\n\\ng1f3\\n\\nPlaying the knight to f3 to control the center and develop towards castling. How will you counter this, PlayerBlack? Your move!\\n\\nThe current board is \\n\\n r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . N . .\\nP P P P . P P P\\nR N B Q K B . R\", 'role': 'assistant'}, {'content': 'd7d6\\n\\nJust a standard Pirc Defense response, PlayerWhite. I hope you are ready for a strategic battle!', 'role': 'user'}, {'content': \"Error: illegal uci: 'e7e5' in rnbqkbnr/ppp2ppp/3p4/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 3\\n\\nd7d6\\n\\nJust a standard Pirc Defense response, PlayerWhite. I hope you are ready for a strategic battle!\\n\\nThe current board is \\n\\n r n b q k b n r\\np p p . . p p p\\n. . . p . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . N . .\\nP P P P . P P P\\nR N B Q K B . R\", 'role': 'assistant'}, {'content': \"e4e5\\n\\nAdvancing the pawn to challenge you right from the start, PlayerBlack. Let's see how you handle this push. It's a bold move in our opening game!\", 'role': 'user'}, {'content': \"e2e4\\n\\nA classic response with the King's Pawn opening. Maybe we'll dance the symphony of the Ruy Lopez, or will it be a wild Sicilian night? Your move, PlayerBlack.\", 'role': 'assistant'}, {'content': 'c7c5\\n\\nThe Sicilian Defense, a dynamic reply to your e4. It seems we might be headed toward a stormy and tactical bout, PlayerWhite. Let the mind games begin!', 'role': 'user'}, {'content': \"e2e4\\n\\nAh, the Sicilian Defense! The battlefield is set for a duel of wits and strategy. May the most cunning player win, PlayerBlack! Let's weave a complex tapestry of moves and counter-moves. Your move!\", 'role': 'assistant'}, {'content': 'c7c5\\n\\nAh, the sweet symphony of pawn tension begins! The Sicilian is a favorite of mine, offering rich possibilities and daring plans. Let the dance of knights and bishops commence, PlayerWhite. Your move!', 'role': 'user'}, {'content': \"e2e4\\n\\nIndeed, the Sicilian always sets the stage for an enthralling game, PlayerBlack. A crossroads of strategies right from the get-go! Let's paint this chess canvas with bold strokes and subtle shades. Your move!\", 'role': 'assistant'}, {'content': \"c7c5\\n\\nAh, PlayerWhite, venturing into the Sicilian Defence indeed promises a vibrant clash of ideas. It's like a dance of pawns and pieces, each move adding a beat to the rhythm of the game. Let's see where this opening leads us! Your move.\", 'role': 'user'}, {'content': \"e2e4\\n\\nThat's right, PlayerBlack! The Sicilian Defense sets the stage for a complex symphony, and with e4, the most popular response, we're well on our way to an intriguing battle. It's as if the kings have sent their armies charging towards each other on an open field. Your move!\", 'role': 'assistant'}, {'content': \"c7c5\\n\\nAh, Sicilian Defense it is – a dash of unpredictability and complex strategies. Let's see how our game unfolds. It's like a dance where each move leads into the next with endless possibilities. Your move, PlayerWhite!\", 'role': 'user'}, {'content': \"e2e4\\n\\nThere we go, the king's pawn opening – classic and bold! The stage is set for an exciting duel, PlayerBlack. The center of the board is like the heart of the battle, and we're both reaching for it. Your move!\", 'role': 'assistant'}], summary=\"e2e4\\n\\nThere we go, the king's pawn opening – classic and bold! The stage is set for an exciting duel, PlayerBlack. The center of the board is like the heart of the battle, and we're both reaching for it. Your move!\", cost=({'total_cost': 0.20591999999999996, 'gpt-4': {'cost': 0.20591999999999996, 'prompt_tokens': 5062, 'completion_tokens': 901, 'total_tokens': 5963}}, {'total_cost': 0}), human_input=[])]" ] }, "execution_count": 4, @@ -738,8 +867,8 @@ " last_message = messages[-1]\n", " if last_message[\"content\"].startswith(\"Error\"):\n", " # try again\n", - " _, rep = recipient.generate_oai_reply(messages + board_state_msg, sender)\n", - " # rep = recipient.generate_reply(messages + board_state_msg, sender)\n", + " # _, rep = recipient.generate_oai_reply(messages + board_state_msg, sender)\n", + " rep = recipient.generate_reply(messages + board_state_msg, sender)\n", " return True, rep\n", " else:\n", " return True, None\n", @@ -763,7 +892,7 @@ " # extract a UCI move from player's message\n", " # TODO: better way to handle this\n", " message[\"content\"] = \"Extract a UCI move from the following message \\n.\" + message.get(\"content\", \"\")\n", - " _, reply = recipient.generate_oai_reply([message], sender)\n", + " _, reply = recipient.generate_oai_reply(recipient.correct_move_messages[sender] + [message], sender)\n", " # reply = recipient.generate_reply(recipient.correct_move_messages[sender] + [message], sender)\n", " uci_move = reply if isinstance(reply, str) else str(reply[\"content\"])\n", " recipient.update_reply_num()\n", @@ -774,9 +903,9 @@ " return (\n", " True,\n", " f\"Error: {e}\"\n", - " + f\"The current board is {recipient.board}\"\n", - " + \"\\n\\nTry again to respond to the following message as if nothing happened.\"\n", - " + sender.context[0].get(\"goal\", \"\"),\n", + " + \"\\n\\n\"\n", + " + sender.context[0].get(\"goal\", \"\")\n", + " + f\"\\n\\nThe current board is \\n\\n {recipient.board}\",\n", " )\n", " else:\n", " # valid move\n", From e12fc308a2be24b6fd011d4c5a869ba7772041ff Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Wed, 14 Feb 2024 21:49:41 -0500 Subject: [PATCH 30/57] nested --- autogen/agentchat/conversable_agent.py | 25 +- notebook/agentchat_nestedchat_optiguide.ipynb | 390 ++++++++---------- test/agentchat/test_nested.py | 63 +-- 3 files changed, 217 insertions(+), 261 deletions(-) diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index 1e2768701fe..6a4e4f76f45 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -313,17 +313,20 @@ def reply_func( @staticmethod def simple_chat_reply(chat_queue, recipient, messages, sender, config): - msg_content = messages[-1].get("content", "") - if not msg_content: - return True, None - else: - # TODO: how to handle message if there are multiple chats (doing this for the first chat for now) - first_chat_info = chat_queue[0] - first_chat_info["message"] = msg_content + "\n" + first_chat_info.get("message", "") - chat_queue[0] = first_chat_info - res = recipient.initiate_chats(chat_queue) - last_res = list(res.values())[-1] - return True, last_res.summary + messages[-1].get("content", "") + for i, c in enumerate(chat_queue): + init_message = c.get("init_message") + if callable(init_message): + init_message = init_message(recipient, messages, sender, config) + if init_message is None: + return True, None + if init_message is None and i == 0: + init_message = messages[-1].get("content", "") + if init_message: + c["message"] = init_message + res = recipient.initiate_chats(chat_queue) + last_res = res[-1] + return True, last_res.summary def register_nested_chats( self, diff --git a/notebook/agentchat_nestedchat_optiguide.ipynb b/notebook/agentchat_nestedchat_optiguide.ipynb index 5fde5c59b0c..a3ce0699128 100644 --- a/notebook/agentchat_nestedchat_optiguide.ipynb +++ b/notebook/agentchat_nestedchat_optiguide.ipynb @@ -131,10 +131,10 @@ "\"\"\"\n", "\n", "SAFEGUARD_SYSTEM_MSG = \"\"\"\n", - "Given the original source code:\n", + "Given the source code:\n", "{source_code}\n", "\n", - "Is the following code safe (not malicious code to break security\n", + "Is the source code safe (not malicious code to break security\n", "and privacy) to run?\n", "Answer only one word.\n", "If not safe, answer `DANGER`; else, answer `SAFE`.\n", @@ -392,10 +392,7 @@ "metadata": {}, "outputs": [], "source": [ - "def nested_writer_reply(chat_queue, recipient, messages, sender, config):\n", - " # max_turn = 10\n", - " # if chat_queue[0][\"recipient\"].reply_num >= max_turn:\n", - " # return True, None\n", + "def writer_init_messsage(recipient, messages, sender, config):\n", " msg_content = messages[-1].get(\"content\", \"\")\n", " # board = config\n", " # get execution result of the original source code\n", @@ -415,80 +412,66 @@ " )\n", " + user_chat_history\n", " )\n", - " # safeguard_sys_msg = (\n", - " # SAFEGUARD_SYSTEM_MSG.format(source_code=recipient.source_code)\n", - " # + user_chat_history\n", - " # )\n", - " # update system message\n", - " nested_agent = chat_queue[0][\"recipient\"]\n", - " nested_agent.update_system_message(writer_sys_msg)\n", - " nested_agent.set_user_chat_history(user_chat_history)\n", - " # safeguard.update_system_message(safeguard_sys_msg) #TODO: update system message for safeguard\n", - " nested_agent.reset()\n", + "\n", " # safeguard.reset() #TODO: reset safeguard\n", " recipient.debug_times_left = recipient.debug_times\n", " recipient.set_success(False)\n", + " return writer_sys_msg + \"\\n\" + CODE_PROMPT\n", "\n", - " chat_queue[0][\"recipient\"] = nested_agent\n", - " chat_queue[0][\"message\"] = CODE_PROMPT\n", - "\n", - " chat_res = recipient.initiate_chats(chat_queue)\n", "\n", - " if recipient.success:\n", - " reply = list(chat_res.values())[-1].summary\n", + "def writer_success_summary(recipient, sender):\n", + " if sender.success:\n", + " return sender.last_message(recipient)[\"content\"].replace(\"TERMINATE\", \"\")\n", " else:\n", - " reply = \"Sorry. I cannot answer your question.\"\n", - " return True, reply\n", - "\n", + " return \"Sorry. I cannot answer your question.\"\n", "\n", - "def nested_safeguard_reply(chat_queue, recipient, messages, sender, config):\n", - " if recipient.success:\n", - " # no reply to writer\n", - " return True, None\n", - " # Step 3: safeguard\n", "\n", - " safeguard_sys_msg = SAFEGUARD_SYSTEM_MSG.format(source_code=recipient.source_code) + recipient.user_chat_history\n", - "\n", - " chat_agent = chat_queue[0][\"recipient\"]\n", - " chat_agent.update_system_message(safeguard_sys_msg)\n", - " chat_agent.reset()\n", - " chat_queue[0][\"recipient\"] = chat_agent\n", - " # chat_agent[0][\"recipient\"].set_success(False)\n", + "def safeguard_init_message(recipient, messages, sender, config):\n", " last_msg_content = messages[-1].get(\"content\", \"\")\n", " _, code = extract_code(last_msg_content)[0]\n", - " chat_info = chat_queue[0]\n", - " chat_info[\"message\"] = SAFEGUARD_SYSTEM_MSG.format(source_code=recipient.source_code)\n", - " chat_queue[0] = chat_info\n", - " res = recipient.initiate_chats(chat_queue)\n", + " if _ != \"unknown\":\n", + " return SAFEGUARD_SYSTEM_MSG.format(source_code=code) + sender.user_chat_history\n", + " else:\n", + " return\n", + " # return SAFEGUARD_SYSTEM_MSG.format(source_code=recipient.source_code)\n", + "\n", + "\n", + "def safeguard_summary(recipient, sender):\n", + " if sender.success:\n", + " return None\n", + " safe_msg = sender.last_message(recipient)[\"content\"].replace(\"TERMINATE\", \"\")\n", "\n", - " safe_msg = list(res.values())[-1].summary\n", " if safe_msg.find(\"DANGER\") < 0:\n", " # Step 4 and 5: Run the code and obtain the results\n", - " src_code = insert_code(recipient.source_code, code)\n", + " src_code = insert_code(sender.source_code, code)\n", " execution_rst = run_with_exec(src_code)\n", " print(colored(str(execution_rst), \"yellow\"))\n", " if type(execution_rst) in [str, int, float]:\n", " # we successfully run the code and get the result\n", - " recipient.set_success(True)\n", + " sender.set_success(True)\n", " # Step 6: request to interpret results\n", - " return True, INTERPRETER_PROMPT.format(execution_rst=execution_rst)\n", + " return INTERPRETER_PROMPT.format(execution_rst=execution_rst)\n", " else:\n", " # DANGER: If not safe, try to debug. Redo coding\n", " execution_rst = \"\"\"\n", " Sorry, this new code is not safe to run. I would not allow you to execute it.\n", " Please try to find a new way (coding) to answer the question.\"\"\"\n", - " if recipient.debug_times_left > 0:\n", + " if sender.debug_times_left > 0:\n", " # Try to debug and write code again (back to step 2)\n", - " recipient.update_debug_times()\n", - " return True, DEBUG_PROMPT.format(error_type=type(execution_rst), error_message=str(execution_rst))\n", + " sender.update_debug_times()\n", + " return DEBUG_PROMPT.format(error_type=type(execution_rst), error_message=str(execution_rst))\n", "\n", "\n", - "writer_chat_queue = [{\"recipient\": writer, \"summary_method\": \"last_msg\"}]\n", - "safeguard_chat_queue = [{\"recipient\": safeguard, \"summary_method\": \"last_msg\"}]\n", + "writer_chat_queue = [\n", + " {\"recipient\": writer, \"summary_method\": writer_success_summary, \"init_message\": writer_init_messsage}\n", + "]\n", + "safeguard_chat_queue = [\n", + " {\"recipient\": safeguard, \"summary_method\": safeguard_summary, \"init_message\": safeguard_init_message}\n", + "]\n", "# writer is triggered only when receiving a message from the user\n", - "optiguide_commander.register_nested_chats(\"user\", writer_chat_queue, nested_writer_reply)\n", + "optiguide_commander.register_nested_chats(\"user\", writer_chat_queue)\n", "# safeguard is triggered only when receiving a message from the writer\n", - "optiguide_commander.register_nested_chats(\"writer\", safeguard_chat_queue, nested_safeguard_reply)" + "optiguide_commander.register_nested_chats(\"writer\", safeguard_chat_queue)" ] }, { @@ -512,36 +495,14 @@ "What if we prohibit shipping from supplier 1 to roastery 2?\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[34mStart a new chat with the following message: \n", - "\n", - "Answer Code:\n", - "\n", - "\n", - "With the following carryover: \n", - "\u001b[0m\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", "\u001b[33mcommander\u001b[0m (to writer):\n", "\n", + "You are a chatbot to:\n", + "(1) write Python code to answer users questions for supply chain-related coding\n", + "project;\n", + "(2) explain solutions from a Gurobi/Python solver.\n", "\n", - "Answer Code:\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mwriter\u001b[0m (to commander):\n", - "\n", - "```python\n", - "model.addConstr(x['supplier1', 'roastery2'] == 0, \"prohibit_s1_r2\")\n", - "```\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[34mStart a new chat with the following message: \n", - "\n", - "Given the original source code:\n", + "--- SOURCE CODE ---\n", "import time\n", "\n", "from gurobipy import GRB, Model\n", @@ -651,130 +612,73 @@ " print(\"Not solved to optimality. Optimization status:\", m.status)\n", "\n", "\n", - "Is the following code safe (not malicious code to break security\n", - "and privacy) to run?\n", - "Answer only one word.\n", - "If not safe, answer `DANGER`; else, answer `SAFE`.\n", - "\n", - "\n", - "With the following carryover: \n", - "\u001b[0m\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[33mcommander\u001b[0m (to safeguard):\n", - "\n", + "--- DOC STR ---\n", "\n", - "Given the original source code:\n", - "import time\n", + "---\n", "\n", - "from gurobipy import GRB, Model\n", + "Here are some example questions and their answers and codes:\n", + "--- EXAMPLES ---\n", "\n", - "# Example data\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", - "capacity_in_supplier = {'supplier1': 150, 'supplier2': 50, 'supplier3': 100}\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", - "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", "\n", - "roasting_cost_dark = {'roastery1': 5, 'roastery2': 6}\n", + "The execution result of the original source code is below.\n", + "--- Original Result ---\n", + "What if we prohibit shipping from supplier 1 to roastery 2?\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", + "Note that your written code will be added to the lines with substring:\n", + "\"# OPTIGUIDE *** CODE GOES HERE\"\n", + "So, you don't need to write other code, such as m.optimize() or m.update().\n", + "You just need to write code snippet in ```python ...``` block.\n", "\n", - "light_coffee_needed_for_cafe = {'cafe1': 20, 'cafe2': 30, 'cafe3': 40}\n", + "Here are the history of discussions:\n", + "[{'content': 'What if we prohibit shipping from supplier 1 to roastery 2?', 'role': 'user'}]\n", "\n", - "dark_coffee_needed_for_cafe = {'cafe1': 20, 'cafe2': 20, 'cafe3': 100}\n", + "Answer Code:\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", - "# Create a new model\n", - "model = Model(\"coffee_distribution\")\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mwriter\u001b[0m (to commander):\n", "\n", - "# OPTIGUIDE DATA CODE GOES HERE\n", + "To answer the user's question regarding the prohibition of shipping from supplier 1 to roastery 2, we need to add a constraint to the optimization model that sets the shipping quantity `x` for the (`supplier1`, `roastery2`) route to zero. This will effectively disallow any shipping from supplier 1 to roastery 2. The code to do this should be placed in the section of the source code that is indicated by the comment \"# OPTIGUIDE DATA CODE GOES HERE\".\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", + "Here's the Python code snippet to add this constraint:\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] 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", + "```python\n", + "# filename: supply_chain_code.py\n", + "model.addConstr(x[('supplier1', 'roastery2')] == 0, \"prohibit_supplier1_roastery2\")\n", + "```\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] for i in shipping_cost_from_supplier_to_roastery.keys()\n", - " if i[0] == s) <= capacity_in_supplier[s], f\"supply_{s}\")\n", + "Adding this constraint will prevent the optimizer from selecting any transport from `supplier1` to `roastery2`, which is what the prohibition is intended to achieve.\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] 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] for j in shipping_cost_from_roastery_to_cafe.keys()\n", - " if j[1] == c) >= dark_coffee_needed_for_cafe[c],\n", - " f\"dark_demand_{c}\")\n", + "Instruct the user to add this code snippet in the appropriate place in the source code that they have provided. After adding this code, the `m.optimize()` function will solve the modified version of the problem that includes the new constraint.\n", "\n", - "# Optimize model\n", - "model.optimize()\n", - "m = model\n", - "\n", - "# OPTIGUIDE CONSTRAINT CODE GOES HERE\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mcommander\u001b[0m (to safeguard):\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", + "Given the source code:\n", + "# filename: supply_chain_code.py\n", + "model.addConstr(x[('supplier1', 'roastery2')] == 0, \"prohibit_supplier1_roastery2\")\n", "\n", - "\n", - "Is the following code safe (not malicious code to break security\n", + "Is the source code safe (not malicious code to break security\n", "and privacy) to run?\n", "Answer only one word.\n", "If not safe, answer `DANGER`; else, answer `SAFE`.\n", @@ -798,18 +702,18 @@ "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0x87036b63\n", + "Model fingerprint: 0x37321acf\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", @@ -821,7 +725,7 @@ "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 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", @@ -830,44 +734,92 @@ "CPU model: Apple M3 Max\n", "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", - "Optimize a model with 12 rows, 18 columns and 37 nonzeros\n", - "Model fingerprint: 0xe577deca\n", + "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", + "Model fingerprint: 0x37321acf\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 prohibit_s1_r2 by 80.000000000\n", - "\n", - "Found heuristic solution: objective 2920.0000000\n", - "Presolve removed 2 rows and 1 columns\n", + "Found heuristic solution: objective 3490.0000000\n", "Presolve time: 0.00s\n", - "Presolved: 10 rows, 17 columns, 33 nonzeros\n", - "Variable types: 0 continuous, 17 integer (0 binary)\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.760000e+03, 10 iterations, 0.00 seconds (0.00 work units)\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 2760.0000000 2760.00000 0.00% - 0s\n", + "* 0 0 0 2470.0000000 2470.00000 0.00% - 0s\n", + "\n", + "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n", + "Thread count was 14 (of 14 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 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n", + "\n", + "CPU model: Apple M3 Max\n", + "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", + "\n", + "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", + "Model fingerprint: 0x37321acf\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", - "Explored 1 nodes (10 simplex iterations) in 0.00 seconds (0.00 work units)\n", + "\n", + "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 available processors)\n", "\n", - "Solution count 2: 2760 2920 \n", + "Solution count 3: 2470 3486 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", - "Fri Feb 9 23:39:06 2024\n", - "Optimal cost: 2760.0\n", - "\u001b[33mOptimization problem solved. The objective value is: 2760.0\u001b[0m\n", + "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", + "Wed Feb 14 21:46:15 2024\n", + "Optimal cost: 2470.0\n", + "Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n", + "\n", + "CPU model: Apple M3 Max\n", + "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", + "\n", + "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", + "Model fingerprint: 0x37321acf\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.00 seconds (0.00 work units)\n", + "Thread count was 14 (of 14 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", + "Wed Feb 14 21:46:15 2024\n", + "Optimal cost: 2470.0\n", + "\u001b[33mOptimization problem solved. The objective value is: 2470.0\u001b[0m\n", "\u001b[33mcommander\u001b[0m (to writer):\n", "\n", - "Here are the execution results: Optimization problem solved. The objective value is: 2760.0\n", + "Here are the execution results: Optimization problem solved. The objective value is: 2470.0\n", "\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\n", @@ -879,34 +831,28 @@ "--------------------------------------------------------------------------------\n", "\u001b[33mwriter\u001b[0m (to commander):\n", "\n", - "Certainly, the results from optimizing the supply chain model suggest the following:\n", - "\n", - "After adding the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was still successfully solved. The new objective value, which represents the total cost of the supply chain operation after implementing the constraint, is $2760.0.\n", + "Certainly! Here's a human-readable answer:\n", "\n", - "To provide a comparison with the original results, we should look at the objective value before the constraint was added. If the previous objective value is lower than $2760.0, this implies that the new constraint increased the total cost of operations, highlighting the economic impact of not using supplier 1 for roastery 2.\n", + "After integrating the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was solved with a total cost of $2470.0.\n", "\n", - "On the other hand, if the previous objective value is higher than $2760.0, then despite the constraint, we have found a more cost-effective way to meet the supply and demand needs.\n", + "To provide context to this result, we need to compare it with the original total cost prior to the implementation of the new constraint. However, the original total cost was not provided in the information given to me. If the original total cost was known, we would compare the two to understand the financial impact of this constraint on the supply chain.\n", "\n", - "Unfortunately, the original objective value prior to this constraint being added is not provided here. For a full comparison, you would need that original value to assess the financial impact of this constraint on the overall supply chain cost.\n", + "If prohibiting shipping from supplier 1 to roastery 2 results in a higher cost than the original scenario, it suggests that the previous ability to ship from supplier 1 to roastery 2 had been cost-effective and beneficial for minimizing overall expenses. On the other hand, if the cost is the same or lower, then shipping from supplier 1 to roastery 2 may not have been crucial for the cost optimization of the supply chain, or other routes may have been able to compensate effectively for this restriction.\n", "\n", - "--- HUMAN READABLE ANSWER ---\n", - "The new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.\n", + "Without the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mcommander\u001b[0m (to user):\n", "\n", - "Certainly, the results from optimizing the supply chain model suggest the following:\n", + "Certainly! Here's a human-readable answer:\n", "\n", - "After adding the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was still successfully solved. The new objective value, which represents the total cost of the supply chain operation after implementing the constraint, is $2760.0.\n", + "After integrating the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was solved with a total cost of $2470.0.\n", "\n", - "To provide a comparison with the original results, we should look at the objective value before the constraint was added. If the previous objective value is lower than $2760.0, this implies that the new constraint increased the total cost of operations, highlighting the economic impact of not using supplier 1 for roastery 2.\n", + "To provide context to this result, we need to compare it with the original total cost prior to the implementation of the new constraint. However, the original total cost was not provided in the information given to me. If the original total cost was known, we would compare the two to understand the financial impact of this constraint on the supply chain.\n", "\n", - "On the other hand, if the previous objective value is higher than $2760.0, then despite the constraint, we have found a more cost-effective way to meet the supply and demand needs.\n", + "If prohibiting shipping from supplier 1 to roastery 2 results in a higher cost than the original scenario, it suggests that the previous ability to ship from supplier 1 to roastery 2 had been cost-effective and beneficial for minimizing overall expenses. On the other hand, if the cost is the same or lower, then shipping from supplier 1 to roastery 2 may not have been crucial for the cost optimization of the supply chain, or other routes may have been able to compensate effectively for this restriction.\n", "\n", - "Unfortunately, the original objective value prior to this constraint being added is not provided here. For a full comparison, you would need that original value to assess the financial impact of this constraint on the overall supply chain cost.\n", - "\n", - "--- HUMAN READABLE ANSWER ---\n", - "The new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.\n", + "Without the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\n", "\n", "--------------------------------------------------------------------------------\n" ] @@ -914,7 +860,7 @@ { "data": { "text/plain": [ - "ChatResult(chat_history=[{'content': 'What if we prohibit shipping from supplier 1 to roastery 2?', 'role': 'assistant'}, {'content': 'Certainly, the results from optimizing the supply chain model suggest the following:\\n\\nAfter adding the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was still successfully solved. The new objective value, which represents the total cost of the supply chain operation after implementing the constraint, is $2760.0.\\n\\nTo provide a comparison with the original results, we should look at the objective value before the constraint was added. If the previous objective value is lower than $2760.0, this implies that the new constraint increased the total cost of operations, highlighting the economic impact of not using supplier 1 for roastery 2.\\n\\nOn the other hand, if the previous objective value is higher than $2760.0, then despite the constraint, we have found a more cost-effective way to meet the supply and demand needs.\\n\\nUnfortunately, the original objective value prior to this constraint being added is not provided here. For a full comparison, you would need that original value to assess the financial impact of this constraint on the overall supply chain cost.\\n\\n--- HUMAN READABLE ANSWER ---\\nThe new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.', 'role': 'user'}], summary='Certainly, the results from optimizing the supply chain model suggest the following:\\n\\nAfter adding the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was still successfully solved. The new objective value, which represents the total cost of the supply chain operation after implementing the constraint, is $2760.0.\\n\\nTo provide a comparison with the original results, we should look at the objective value before the constraint was added. If the previous objective value is lower than $2760.0, this implies that the new constraint increased the total cost of operations, highlighting the economic impact of not using supplier 1 for roastery 2.\\n\\nOn the other hand, if the previous objective value is higher than $2760.0, then despite the constraint, we have found a more cost-effective way to meet the supply and demand needs.\\n\\nUnfortunately, the original objective value prior to this constraint being added is not provided here. For a full comparison, you would need that original value to assess the financial impact of this constraint on the overall supply chain cost.\\n\\n--- HUMAN READABLE ANSWER ---\\nThe new optimization, with shipping from supplier 1 to roastery 2 prohibited, resulted in a total cost of $2760.0. Without the initial result provided, it is difficult to directly compare, but this new result sets a benchmark for cost with the current constraints in place. To fully understand the impact, the objective value without this constraint (from the original model) must be reviewed for comparison.', cost=({'total_cost': 0.0465, 'gpt-4': {'cost': 0.0465, 'prompt_tokens': 1544, 'completion_tokens': 3, 'total_tokens': 1547}}, {'total_cost': 0}), human_input=[])" + "ChatResult(chat_history=[{'content': 'What if we prohibit shipping from supplier 1 to roastery 2?', 'role': 'assistant'}, {'content': \"Certainly! Here's a human-readable answer:\\n\\nAfter integrating the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was solved with a total cost of $2470.0.\\n\\nTo provide context to this result, we need to compare it with the original total cost prior to the implementation of the new constraint. However, the original total cost was not provided in the information given to me. If the original total cost was known, we would compare the two to understand the financial impact of this constraint on the supply chain.\\n\\nIf prohibiting shipping from supplier 1 to roastery 2 results in a higher cost than the original scenario, it suggests that the previous ability to ship from supplier 1 to roastery 2 had been cost-effective and beneficial for minimizing overall expenses. On the other hand, if the cost is the same or lower, then shipping from supplier 1 to roastery 2 may not have been crucial for the cost optimization of the supply chain, or other routes may have been able to compensate effectively for this restriction.\\n\\nWithout the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\", 'role': 'user'}], summary=\"Certainly! Here's a human-readable answer:\\n\\nAfter integrating the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was solved with a total cost of $2470.0.\\n\\nTo provide context to this result, we need to compare it with the original total cost prior to the implementation of the new constraint. However, the original total cost was not provided in the information given to me. If the original total cost was known, we would compare the two to understand the financial impact of this constraint on the supply chain.\\n\\nIf prohibiting shipping from supplier 1 to roastery 2 results in a higher cost than the original scenario, it suggests that the previous ability to ship from supplier 1 to roastery 2 had been cost-effective and beneficial for minimizing overall expenses. On the other hand, if the cost is the same or lower, then shipping from supplier 1 to roastery 2 may not have been crucial for the cost optimization of the supply chain, or other routes may have been able to compensate effectively for this restriction.\\n\\nWithout the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\", cost=({'total_cost': 0.016919999999999998, 'gpt-4': {'cost': 0.016919999999999998, 'prompt_tokens': 558, 'completion_tokens': 3, 'total_tokens': 561}}, {'total_cost': 0}), human_input=[])" ] }, "execution_count": 5, diff --git a/test/agentchat/test_nested.py b/test/agentchat/test_nested.py index f826a1eeb19..c765b59b6a1 100644 --- a/test/agentchat/test_nested.py +++ b/test/agentchat/test_nested.py @@ -116,6 +116,7 @@ class BoardAgent(autogen.AssistantAgent): board: chess.Board = chess.Board() correct_move_messages: Dict[autogen.Agent, List[Dict]] = defaultdict(list) _reply_num: int = 0 + context: Optional[Dict] = None def set_correct_move_messages(self, sender, message, uci_move): self.correct_move_messages[sender].extend([message, self._message_to_dict(uci_move)]) @@ -158,6 +159,15 @@ def reply_num(self): max_consecutive_auto_reply=max_turn, ) + def player2board_init_message(recipient, messages, sender, config): + board = recipient.board + board_state_msg = [{"role": "system", "content": f"Current board:\n{board}"}] + useful_msg = messages[-1].copy() + useful_msg["content"] = useful_msg.get("content", "") + f"The current board is:\n {board} ." + oai_messages = [messages[-1]] + _, message = recipient.generate_oai_reply(oai_messages + board_state_msg, sender) + return message + def player2board_reply(recipient, messages, sender, config): board = config if config else "" # add a system message about the current state of the board. @@ -172,23 +182,6 @@ def player2board_reply(recipient, messages, sender, config): else: return True, None - def board_chat_func(chat_queue, recipient, messages, sender, config): - # TODO: better way to handle this - if chat_queue[0]["recipient"].reply_num >= max_turn: - return True, None - c = chat_queue[0] # board = config - board = c["recipient"].board - board_state_msg = [{"role": "system", "content": f"Current board:\n{board}"}] - useful_msg = messages[-1].copy() - useful_msg["content"] = useful_msg.get("content", "") + f"The current board is:\n {board} ." - oai_messages = [messages[-1]] - _, message = recipient.generate_oai_reply(oai_messages + board_state_msg, sender) - c["message"] = message - chat_queue[0] = c - res = recipient.initiate_chats(chat_queue) - last_res = list(res.values())[-1] - return True, last_res.summary - def board_reply(recipient, messages, sender, config): if recipient.reply_num >= max_turn: return True, None @@ -205,7 +198,7 @@ def board_reply(recipient, messages, sender, config): recipient.board.push_uci(uci_move) except ValueError as e: # invalid move - return True, f"Error: {e}" + return True, f"Error: {e}" + "\n\nTry again as if nothing happened." else: # valid move m = chess.Move.from_uci(uci_move) @@ -217,25 +210,39 @@ def board_reply(recipient, messages, sender, config): ) except NameError as e: print(f"Error displaying board: {e}") - # better way to handle this - recipient.set_correct_move_messages(sender, message, uci_move) - recipient.correct_move_messages[sender][-1]["role"] = "assistant" - return True, org_msg.get("content", "") # + "\n Move:" + uci_move + # better way to handle this + recipient.set_correct_move_messages(sender, message, uci_move) + recipient.correct_move_messages[sender][-1]["role"] = "assistant" + return True, org_msg.get("content", "") # + "\n Move:" + uci_move - board_agent.register_reply([white_player_name, black_player_name], board_reply, 0) player_white.register_nested_chats( black_player_name, - [{"recipient": board_agent, "summary_method": "last_msg"}], - board_chat_func, + [ + { + "sender": player_white, + "recipient": board_agent, + "init_message": player2board_init_message, + "summary_method": "last_msg", + } + ], + config=board_agent.board, ) player_black.register_nested_chats( white_player_name, - [{"recipient": board_agent, "summary_method": "last_msg"}], - board_chat_func, + [ + { + "sender": player_black, + "recipient": board_agent, + "init_message": player2board_init_message, + "summary_method": "last_msg", + } + ], + config=board_agent.board, ) player_white.register_reply(BoardAgent, player2board_reply) player_black.register_reply(BoardAgent, player2board_reply) - player_white.initiate_chat(player_black, message="Your turn.") + board_agent.register_reply([white_player_name, black_player_name], board_reply, 0) + autogen.initiate_chats([{"sender": player_white, "recipient": player_black, "message": "Your turn."}]) if __name__ == "__main__": From 99e4314941908108622880e2f1d7febd7a3acfd5 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Thu, 15 Feb 2024 16:21:56 -0500 Subject: [PATCH 31/57] update SoM --- autogen/agentchat/conversable_agent.py | 11 +- ...agentchat_nestedchat_Society_of_Mind.ipynb | 333 +++++++++--------- 2 files changed, 166 insertions(+), 178 deletions(-) diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index 6a4e4f76f45..0204bc0747b 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -313,18 +313,21 @@ def reply_func( @staticmethod def simple_chat_reply(chat_queue, recipient, messages, sender, config): - messages[-1].get("content", "") - for i, c in enumerate(chat_queue): + current_chat_queue = chat_queue.copy() + last_msg = messages[-1].get("content", "") + for i, c in enumerate(current_chat_queue): init_message = c.get("init_message") if callable(init_message): init_message = init_message(recipient, messages, sender, config) if init_message is None: return True, None if init_message is None and i == 0: - init_message = messages[-1].get("content", "") + init_message = last_msg if init_message: c["message"] = init_message - res = recipient.initiate_chats(chat_queue) + else: + return True, None + res = recipient.initiate_chats(current_chat_queue) last_res = res[-1] return True, last_res.summary diff --git a/notebook/agentchat_nestedchat_Society_of_Mind.ipynb b/notebook/agentchat_nestedchat_Society_of_Mind.ipynb index d72baab54e4..a52ab47f9a4 100644 --- a/notebook/agentchat_nestedchat_Society_of_Mind.ipynb +++ b/notebook/agentchat_nestedchat_Society_of_Mind.ipynb @@ -117,7 +117,6 @@ "\u001b[34mStart a new chat with the following message: \n", "On which days this year was Microsoft Stock higher than $400? Put results in a table.\n", "\n", - "\n", "With the following carryover: \n", "\u001b[0m\n", "\u001b[34m\n", @@ -126,87 +125,74 @@ "\n", "On which days this year was Microsoft Stock higher than $400? Put results in a table.\n", "\n", - "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", "\n", - "To find out on which days Microsoft Stock (MSFT) was higher than $400 in the current year, we would typically query a financial data API or use a library such as `pandas` with `pandas_datareader` to extract historical stock price data. However, since I can't execute live web queries or directly access external databases, the task will require the following steps:\n", + "To determine on which days Microsoft (ticker symbol: MSFT) stock was trading higher than $400 this year, I will use Python with the `pandas` library and the `yfinance` package to fetch historical stock price data from Yahoo Finance. To get this data, you must first ensure that `pandas` and `yfinance` are installed in your Python environment. If they're not installed, you can install them using pip.\n", "\n", - "1. Retrieve historical stock price data for Microsoft for the current year up to the present day.\n", - "2. Filter the data to identify the days on which the stock price was higher than $400.\n", - "3. Format the filtered data into a table.\n", + "Here's a step-by-step plan we're going to follow:\n", "\n", - "As an AI assistant, I can provide you with the Python code which you can execute locally to perform this task. You'll need to install the `pandas` and `pandas_datareader` libraries if you haven't already. You can install them using the following commands in your shell:\n", - "\n", - "```sh\n", - "pip install pandas pandas_datareader\n", - "```\n", + "1. Import the required libraries.\n", + "2. Set up the ticker symbol for Microsoft.\n", + "3. Define the date range for \"this year\" until today.\n", + "4. Use yfinance to download the stock data for Microsoft.\n", + "5. Filter the data to find the days when the stock was higher than $400.\n", + "6. Print out the result in a tabular format.\n", "\n", - "After you have the necessary libraries installed, you can run the following Python code to retrieve the historical stock price data for Microsoft and identify the days on which the stock price exceeded $400:\n", + "Please run the following code:\n", "\n", "```python\n", - "# filename: find_msft_higher_than_400.py\n", + "# filename: msft_stock_days_above_400.py\n", "import pandas as pd\n", - "import pandas_datareader as pdr\n", + "import yfinance as yf\n", "from datetime import datetime\n", "\n", - "# Set the year for which we're retrieving the data\n", - "year_to_check = datetime.now().year\n", - "start_date = f'{year_to_check}-01-01'\n", - "end_date = datetime.now().strftime('%Y-%m-%d')\n", - "\n", - "# Get Microsoft Stock data for the current year\n", - "msft_data = pdr.DataReader('MSFT', 'yahoo', start_date, end_date)\n", - "\n", - "# Filter days where Close price was higher than $400\n", - "days_above_400 = msft_data[msft_data['Close'] > 400]\n", - "\n", - "# Check if there are any days where the stock was above $400\n", - "if not days_above_400.empty:\n", - " # Format results into a table and print\n", - " print(days_above_400[['Close']].style.set_caption(\n", - " f\"Days in {year_to_check} when MSFT stock price closed above $400\"\n", - " ))\n", - "else:\n", - " print(f\"No days in {year_to_check} when MSFT stock price closed above $400.\")\n", + "# Ensure that pandas and yfinance are installed\n", + "try:\n", + " import pandas\n", + " import yfinance\n", + "except ImportError as e:\n", + " print(\"Please install pandas and yfinance using 'pip install pandas yfinance'\")\n", + " raise e\n", + "\n", + "def find_msft_days_above_400():\n", + " # Ticker symbol for Microsoft\n", + " ticker_symbol = \"MSFT\"\n", + " \n", + " # get the current year\n", + " current_year = datetime.now().year\n", + "\n", + " # Define the date range for this year until today\n", + " start_date = f\"{current_year}-01-01\"\n", + " end_date = datetime.now().strftime('%Y-%m-%d')\n", + "\n", + " # Download the stock price data\n", + " msft_data = yf.download(ticker_symbol, start=start_date, end=end_date)\n", + "\n", + " # Filter the data for days when the closing price was higher than $400\n", + " days_above_400 = msft_data[msft_data['Close'] > 400]\n", + "\n", + " # Check if there are any days that the closing price was above $400\n", + " if not days_above_400.empty:\n", + " # Print the result in a tabular format\n", + " print(days_above_400[['Close']].to_markdown())\n", + " else:\n", + " print(f\"No days in {current_year} where the Microsoft stock closed above $400.\")\n", + "\n", + "if __name__ == \"__main__\":\n", + " find_msft_days_above_400()\n", "```\n", "\n", - "Before running the code, ensure you have an internet connection as the code will retrieve data from the web.\n", - "\n", - "Here's the code explanation:\n", - "1. Import the necessary libraries (`pandas`, `pandas_datareader`, and `datetime`).\n", - "2. Define the current year and the range of dates to query data for.\n", - "3. Use `pandas_datareader` to download the MSFT stock data from Yahoo Finance for the specified date range.\n", - "4. Filter the data to get only the days where the `'Close'` price is greater than $400.\n", - "5. Check if the filtered frame is empty (which would mean there were no days with a closing price above $400), and create a stylized table with the results if not empty.\n", - "6. Print the resulting table or a message stating there were no days the condition was met.\n", - "\n", - "Run the provided Python script to get your results. If you run into any issues or receive an error, please let me know, and I will provide further assistance.\n", + "Please save the above code to a file and run it using Python. It will print the days when Microsoft stock was higher than $400 this year, if any, in a markdown table format. If the stock has not surpassed that value this year, the script will tell you so.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", - ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is sh)...\u001b[0m\n", - "\u001b[31m\n", - ">>>>>>>> EXECUTING CODE BLOCK 1 (inferred language is python)...\u001b[0m\n", + ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n", "\u001b[33mInner-code-interpreter\u001b[0m (to chat_manager):\n", "\n", "exitcode: 1 (execution failed)\n", "Code output: \n", - "Requirement already satisfied: pandas in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (2.2.0)\n", - "Requirement already satisfied: pandas_datareader in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (0.10.0)\n", - "Requirement already satisfied: numpy<2,>=1.22.4 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from pandas) (1.26.3)\n", - "Requirement already satisfied: python-dateutil>=2.8.2 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from pandas) (2.8.2)\n", - "Requirement already satisfied: pytz>=2020.1 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from pandas) (2023.3.post1)\n", - "Requirement already satisfied: tzdata>=2022.7 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from pandas) (2023.4)\n", - "Requirement already satisfied: lxml in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from pandas_datareader) (5.1.0)\n", - "Requirement already satisfied: requests>=2.19.0 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from pandas_datareader) (2.31.0)\n", - "Requirement already satisfied: six>=1.5 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from python-dateutil>=2.8.2->pandas) (1.16.0)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from requests>=2.19.0->pandas_datareader) (3.3.2)\n", - "Requirement already satisfied: idna<4,>=2.5 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from requests>=2.19.0->pandas_datareader) (3.6)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from requests>=2.19.0->pandas_datareader) (2.1.0)\n", - "Requirement already satisfied: certifi>=2017.4.17 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from requests>=2.19.0->pandas_datareader) (2023.11.17)\n", - "\n", - "find_msft_higher_than_400.py:2: DeprecationWarning: \n", + "msft_stock_days_above_400.py:2: DeprecationWarning: \n", "Pyarrow will become a required dependency of pandas in the next major release of pandas (pandas 3.0),\n", "(to allow more performant data types, such as the Arrow string type, and better interoperability with other libraries)\n", "but was not found to be installed on your system.\n", @@ -214,152 +200,151 @@ "please provide us feedback at https://github.com/pandas-dev/pandas/issues/54466\n", " \n", " import pandas as pd\n", + "/Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages/yfinance/utils.py:775: FutureWarning: The 'unit' keyword in TimedeltaIndex construction is deprecated and will be removed in a future version. Use pd.to_timedelta instead.\n", + " df.index += _pd.TimedeltaIndex(dst_error_hours, 'h')\n", + "\n", + "[*********************100%%**********************] 1 of 1 completed\n", "Traceback (most recent call last):\n", - " File \"find_msft_higher_than_400.py\", line 12, in \n", - " msft_data = pdr.DataReader('MSFT', 'yahoo', start_date, end_date)\n", - " File \"/Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages/pandas/util/_decorators.py\", line 213, in wrapper\n", + " File \"/Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages/pandas/compat/_optional.py\", line 135, in import_optional_dependency\n", + " module = importlib.import_module(name)\n", + " File \"/Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/importlib/__init__.py\", line 126, in import_module\n", + " return _bootstrap._gcd_import(name[level:], package, level)\n", + " File \"\", line 1050, in _gcd_import\n", + " File \"\", line 1027, in _find_and_load\n", + " File \"\", line 1004, in _find_and_load_unlocked\n", + "ModuleNotFoundError: No module named 'tabulate'\n", + "\n", + "During handling of the above exception, another exception occurred:\n", + "\n", + "Traceback (most recent call last):\n", + " File \"msft_stock_days_above_400.py\", line 39, in \n", + " find_msft_days_above_400()\n", + " File \"msft_stock_days_above_400.py\", line 34, in find_msft_days_above_400\n", + " print(days_above_400[['Close']].to_markdown())\n", + " File \"/Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages/pandas/util/_decorators.py\", line 333, in wrapper\n", " return func(*args, **kwargs)\n", - " File \"/Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages/pandas_datareader/data.py\", line 379, in DataReader\n", - " ).read()\n", - " File \"/Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages/pandas_datareader/base.py\", line 253, in read\n", - " df = self._read_one_data(self.url, params=self._get_params(self.symbols))\n", - " File \"/Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages/pandas_datareader/yahoo/daily.py\", line 153, in _read_one_data\n", - " data = j[\"context\"][\"dispatcher\"][\"stores\"][\"HistoricalPriceStore\"]\n", - "TypeError: string indices must be integers\n", + " File \"/Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages/pandas/core/frame.py\", line 2971, in to_markdown\n", + " tabulate = import_optional_dependency(\"tabulate\")\n", + " File \"/Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages/pandas/compat/_optional.py\", line 138, in import_optional_dependency\n", + " raise ImportError(msg)\n", + "ImportError: Missing optional dependency 'tabulate'. Use pip or conda to install tabulate.\n", "\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", "\n", - "The error you're receiving indicates that there is a problem with the retrieval of the stock data. It seems that the structure of the data returned by Yahoo Finance might have changed, or there could be a version mismatch with the `pandas_datareader` library.\n", - "\n", - "Instead of trying to fix the compatibility issues with `pandas_datareader` and Yahoo Finance directly, I will provide you with an alternative using `yfinance` which is a library that can give us the data we need without these issues.\n", + "It looks like the script I provided to you relies on the optional dependency 'tabulate' which is not currently installed in your Python environment. We will need to modify the script to avoid using `to_markdown()` which requires 'tabulate', and instead use a simple formatted string to display the data.\n", "\n", - "First, you'll need to install the `yfinance` library if you haven't already. You can install it using the following command in your shell:\n", - "\n", - "```sh\n", - "pip install yfinance\n", - "```\n", - "\n", - "After installing `yfinance`, run the following Python code:\n", + "Here's the modified code:\n", "\n", "```python\n", - "# filename: find_msft_higher_than_400.py\n", - "import yfinance as yf\n", + "# filename: msft_stock_days_above_400.py\n", "import pandas as pd\n", + "import yfinance as yf\n", "from datetime import datetime\n", "\n", - "# Set the year for which we're retrieving the data\n", - "year_to_check = datetime.now().year\n", - "start_date = f'{year_to_check}-01-01'\n", - "end_date = datetime.now().strftime('%Y-%m-%d')\n", - "\n", - "# Get Microsoft Stock data for the current year\n", - "msft = yf.Ticker(\"MSFT\")\n", - "msft_data = msft.history(start=start_date, end=end_date)\n", - "\n", - "# Filter days where Close price was higher than $400\n", - "days_above_400 = msft_data[msft_data['Close'] > 400]\n", - "\n", - "# Check if there are any days where the stock was above $400\n", - "if not days_above_400.empty:\n", - " # Format results into a table and print\n", - " print(days_above_400[['Close']].rename(columns={'Close': 'Closing Price'}))\n", - "else:\n", - " print(f\"No days in {year_to_check} when MSFT stock price closed above $400.\")\n", + "# Ensure that pandas and yfinance are installed\n", + "try:\n", + " import pandas\n", + " import yfinance\n", + "except ImportError as e:\n", + " print(\"Please install pandas and yfinance using 'pip install pandas yfinance'\")\n", + " raise e\n", + "\n", + "def find_msft_days_above_400():\n", + " # Ticker symbol for Microsoft\n", + " ticker_symbol = \"MSFT\"\n", + " \n", + " # get the current year\n", + " current_year = datetime.now().year\n", + "\n", + " # Define the date range for this year until today\n", + " start_date = f\"{current_year}-01-01\"\n", + " end_date = datetime.now().strftime('%Y-%m-%d')\n", + "\n", + " # Download the stock price data\n", + " msft_data = yf.download(ticker_symbol, start=start_date, end=end_date)\n", + "\n", + " # Filter the data for days when the closing price was higher than $400\n", + " days_above_400 = msft_data[msft_data['Close'] > 400]\n", + "\n", + " # Check if there are any days that the closing price was above $400\n", + " if not days_above_400.empty:\n", + " # Prepare the result as a dataframe with selected columns\n", + " result = days_above_400[['Close']]\n", + " print(\"Date\\t\\tClose Price\")\n", + " print(\"-\"*30)\n", + " # Print each row\n", + " for date, row in result.iterrows():\n", + " print(f\"{date.date()}\\t{row['Close']:.2f}\")\n", + " else:\n", + " print(f\"No days in {current_year} where the Microsoft stock closed above $400.\")\n", + "\n", + "if __name__ == \"__main__\":\n", + " find_msft_days_above_400()\n", "```\n", "\n", - "This script is similar to the previous one but utilizes `yfinance` to get the stock data. It then filters and prints out the days when Microsoft's closing stock price exceeded $400 in a simple table format.\n", - "\n", - "Run the updated Python script, and it should work without any issues. If you encounter further problems, please report back, and I will assist you further.\n", + "This new script will print the dates and the corresponding close prices as a simple text-based table. Please save the script to a file and run it. It should work without the need to install the 'tabulate' package.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", - ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is sh)...\u001b[0m\n", - "\u001b[31m\n", - ">>>>>>>> EXECUTING CODE BLOCK 1 (inferred language is python)...\u001b[0m\n", + ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n", "\u001b[33mInner-code-interpreter\u001b[0m (to chat_manager):\n", "\n", "exitcode: 0 (execution succeeded)\n", "Code output: \n", - "Requirement already satisfied: yfinance in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (0.2.36)\n", - "Requirement already satisfied: pandas>=1.3.0 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from yfinance) (2.2.0)\n", - "Requirement already satisfied: numpy>=1.16.5 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from yfinance) (1.26.3)\n", - "Requirement already satisfied: requests>=2.31 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from yfinance) (2.31.0)\n", - "Requirement already satisfied: multitasking>=0.0.7 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from yfinance) (0.0.11)\n", - "Requirement already satisfied: lxml>=4.9.1 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from yfinance) (5.1.0)\n", - "Requirement already satisfied: appdirs>=1.4.4 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from yfinance) (1.4.4)\n", - "Requirement already satisfied: pytz>=2022.5 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from yfinance) (2023.3.post1)\n", - "Requirement already satisfied: frozendict>=2.3.4 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from yfinance) (2.4.0)\n", - "Requirement already satisfied: peewee>=3.16.2 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from yfinance) (3.17.0)\n", - "Requirement already satisfied: beautifulsoup4>=4.11.1 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from yfinance) (4.12.3)\n", - "Requirement already satisfied: html5lib>=1.1 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from yfinance) (1.1)\n", - "Requirement already satisfied: soupsieve>1.2 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from beautifulsoup4>=4.11.1->yfinance) (2.5)\n", - "Requirement already satisfied: six>=1.9 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from html5lib>=1.1->yfinance) (1.16.0)\n", - "Requirement already satisfied: webencodings in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from html5lib>=1.1->yfinance) (0.5.1)\n", - "Requirement already satisfied: python-dateutil>=2.8.2 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from pandas>=1.3.0->yfinance) (2.8.2)\n", - "Requirement already satisfied: tzdata>=2022.7 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from pandas>=1.3.0->yfinance) (2023.4)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from requests>=2.31->yfinance) (3.3.2)\n", - "Requirement already satisfied: idna<4,>=2.5 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from requests>=2.31->yfinance) (3.6)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from requests>=2.31->yfinance) (2.1.0)\n", - "Requirement already satisfied: certifi>=2017.4.17 in /Users/qingyunwu/miniconda3/envs/ag2/lib/python3.10/site-packages (from requests>=2.31->yfinance) (2023.11.17)\n", - "\n", - " Closing Price\n", - "Date \n", - "2024-01-24 00:00:00-05:00 402.559998\n", - "2024-01-25 00:00:00-05:00 404.869995\n", - "2024-01-26 00:00:00-05:00 403.929993\n", - "2024-01-29 00:00:00-05:00 409.720001\n", - "2024-01-30 00:00:00-05:00 408.589996\n", - "2024-02-01 00:00:00-05:00 403.779999\n", - "2024-02-02 00:00:00-05:00 411.220001\n", - "2024-02-05 00:00:00-05:00 405.649994\n", - "2024-02-06 00:00:00-05:00 405.489990\n", - "2024-02-07 00:00:00-05:00 414.049988\n", - "2024-02-08 00:00:00-05:00 414.109985\n", + "Date\t\tClose Price\n", + "------------------------------\n", + "2024-01-24\t402.56\n", + "2024-01-25\t404.87\n", + "2024-01-26\t403.93\n", + "2024-01-29\t409.72\n", + "2024-01-30\t408.59\n", + "2024-02-01\t403.78\n", + "2024-02-02\t411.22\n", + "2024-02-05\t405.65\n", + "2024-02-06\t405.49\n", + "2024-02-07\t414.05\n", + "2024-02-08\t414.11\n", + "2024-02-09\t420.55\n", + "2024-02-12\t415.26\n", + "2024-02-13\t406.32\n", + "2024-02-14\t409.49\n", "\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", "\n", - "Great, the script has executed successfully and retrieved the dates and closing prices for Microsoft stock on the days it closed above $400 this year. Here is a summary table of the results:\n", + "The Microsoft (MSFT) stock was trading higher than $400 on the following days this year:\n", "\n", - "| Date | Closing Price |\n", - "|-----------------------------|---------------|\n", - "| 2024-01-24 00:00:00-05:00 | $402.56 |\n", - "| 2024-01-25 00:00:00-05:00 | $404.87 |\n", - "| 2024-01-26 00:00:00-05:00 | $403.93 |\n", - "| 2024-01-29 00:00:00-05:00 | $409.72 |\n", - "| 2024-01-30 00:00:00-05:00 | $408.59 |\n", - "| 2024-02-01 00:00:00-05:00 | $403.78 |\n", - "| 2024-02-02 00:00:00-05:00 | $411.22 |\n", - "| 2024-02-05 00:00:00-05:00 | $405.65 |\n", - "| 2024-02-06 00:00:00-05:00 | $405.49 |\n", - "| 2024-02-07 00:00:00-05:00 | $414.05 |\n", - "| 2024-02-08 00:00:00-05:00 | $414.11 |\n", + "```\n", + "Date Close Price\n", + "------------------------------\n", + "2024-01-24 $402.56\n", + "2024-01-25 $404.87\n", + "2024-01-26 $403.93\n", + "2024-01-29 $409.72\n", + "2024-01-30 $408.59\n", + "2024-02-01 $403.78\n", + "2024-02-02 $411.22\n", + "2024-02-05 $405.65\n", + "2024-02-06 $405.49\n", + "2024-02-07 $414.05\n", + "2024-02-08 $414.11\n", + "2024-02-09 $420.55\n", + "2024-02-12 $415.26\n", + "2024-02-13 $406.32\n", + "2024-02-14 $409.49\n", + "```\n", "\n", - "Please note that the dates include the time and timezone offset (Eastern Time Zone). The actual data provided is for the year 2024, which indicates that the current year at the time of this request is 2024.\n", + "These are the days on which the closing price of Microsoft stock exceeded $400. Please note that the dates are based on the stock market calendar and only represent trading days.\n", "\n", "TERMINATE\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mFinancial_assistant_1\u001b[0m (to User):\n", "\n", - "Microsoft stock (MSFT) exceeded a closing price of $400 on the following dates in the year 2024:\n", - "\n", - "- January 24 ($402.56)\n", - "- January 25 ($404.87)\n", - "- January 26 ($403.93)\n", - "- January 29 ($409.72)\n", - "- January 30 ($408.59)\n", - "- February 1 ($403.78)\n", - "- February 2 ($411.22)\n", - "- February 5 ($405.65)\n", - "- February 6 ($405.49)\n", - "- February 7 ($414.05)\n", - "- February 8 ($414.11)\n", - "\n", - "The dates include the time and timezone offset for the Eastern Time Zone.\n", + "Microsoft (MSFT) stock closed higher than $400 on several days in 2024: January 24, January 25, January 26, January 29, January 30, February 1, February 2, February 5, February 6, February 7, February 8, February 9, February 12, February 13, and February 14.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mUser\u001b[0m (to Financial_assistant_1):\n", @@ -372,7 +357,7 @@ { "data": { "text/plain": [ - "ChatResult(chat_history=[{'content': 'On which days this year was Microsoft Stock higher than $400? Put results in a table.', 'role': 'assistant'}, {'content': 'Microsoft stock (MSFT) exceeded a closing price of $400 on the following dates in the year 2024:\\n\\n- January 24 ($402.56)\\n- January 25 ($404.87)\\n- January 26 ($403.93)\\n- January 29 ($409.72)\\n- January 30 ($408.59)\\n- February 1 ($403.78)\\n- February 2 ($411.22)\\n- February 5 ($405.65)\\n- February 6 ($405.49)\\n- February 7 ($414.05)\\n- February 8 ($414.11)\\n\\nThe dates include the time and timezone offset for the Eastern Time Zone.', 'role': 'user'}, {'content': '', 'role': 'assistant'}], summary='', cost=({'total_cost': 0}, {'total_cost': 0}), human_input=[])" + "ChatResult(chat_history=[{'content': 'On which days this year was Microsoft Stock higher than $400? Put results in a table.', 'role': 'assistant'}, {'content': 'Microsoft (MSFT) stock closed higher than $400 on several days in 2024: January 24, January 25, January 26, January 29, January 30, February 1, February 2, February 5, February 6, February 7, February 8, February 9, February 12, February 13, and February 14.', 'role': 'user'}, {'content': '', 'role': 'assistant'}], summary='', cost=({'total_cost': 0}, {'total_cost': 0}), human_input=[])" ] }, "execution_count": 3, From 540f3df91c6cf88df41998a536f3e8576b269cdc Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Thu, 15 Feb 2024 21:54:09 -0500 Subject: [PATCH 32/57] update notebook --- autogen/agentchat/conversable_agent.py | 21 +- notebook/agentchat_nestedchat_optiguide.ipynb | 232 ++++++++++++++++-- 2 files changed, 217 insertions(+), 36 deletions(-) diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index 0204bc0747b..ec80aeefec8 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -313,21 +313,22 @@ def reply_func( @staticmethod def simple_chat_reply(chat_queue, recipient, messages, sender, config): - current_chat_queue = chat_queue.copy() last_msg = messages[-1].get("content", "") - for i, c in enumerate(current_chat_queue): - init_message = c.get("init_message") + chat_queue_to_run = [] + for i, c in enumerate(chat_queue): + current_c = c.copy() + init_message = current_c.get("message") if callable(init_message): init_message = init_message(recipient, messages, sender, config) - if init_message is None: - return True, None - if init_message is None and i == 0: + # By default use the last message as the init message. + elif init_message is None and i == 0: init_message = last_msg if init_message: - c["message"] = init_message - else: - return True, None - res = recipient.initiate_chats(current_chat_queue) + current_c["message"] = init_message + chat_queue_to_run.append(current_c) + if not chat_queue_to_run: + return True, None + res = recipient.initiate_chats(chat_queue_to_run) last_res = res[-1] return True, last_res.summary diff --git a/notebook/agentchat_nestedchat_optiguide.ipynb b/notebook/agentchat_nestedchat_optiguide.ipynb index a3ce0699128..0af3cb60410 100644 --- a/notebook/agentchat_nestedchat_optiguide.ipynb +++ b/notebook/agentchat_nestedchat_optiguide.ipynb @@ -24,7 +24,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 26, "metadata": {}, "outputs": [], "source": [ @@ -67,7 +67,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 27, "metadata": {}, "outputs": [ { @@ -333,7 +333,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 28, "metadata": {}, "outputs": [], "source": [ @@ -388,7 +388,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 29, "metadata": {}, "outputs": [], "source": [ @@ -427,6 +427,8 @@ "\n", "\n", "def safeguard_init_message(recipient, messages, sender, config):\n", + " if recipient.success:\n", + " return None\n", " last_msg_content = messages[-1].get(\"content\", \"\")\n", " _, code = extract_code(last_msg_content)[0]\n", " if _ != \"unknown\":\n", @@ -437,8 +439,6 @@ "\n", "\n", "def safeguard_summary(recipient, sender):\n", - " if sender.success:\n", - " return None\n", " safe_msg = sender.last_message(recipient)[\"content\"].replace(\"TERMINATE\", \"\")\n", "\n", " if safe_msg.find(\"DANGER\") < 0:\n", @@ -462,11 +462,9 @@ " return DEBUG_PROMPT.format(error_type=type(execution_rst), error_message=str(execution_rst))\n", "\n", "\n", - "writer_chat_queue = [\n", - " {\"recipient\": writer, \"summary_method\": writer_success_summary, \"init_message\": writer_init_messsage}\n", - "]\n", + "writer_chat_queue = [{\"recipient\": writer, \"message\": writer_init_messsage, \"summary_method\": writer_success_summary}]\n", "safeguard_chat_queue = [\n", - " {\"recipient\": safeguard, \"summary_method\": safeguard_summary, \"init_message\": safeguard_init_message}\n", + " {\"recipient\": safeguard, \"message\": safeguard_init_message, \"summary_method\": safeguard_summary}\n", "]\n", "# writer is triggered only when receiving a message from the user\n", "optiguide_commander.register_nested_chats(\"user\", writer_chat_queue)\n", @@ -483,7 +481,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 30, "metadata": {}, "outputs": [ { @@ -495,6 +493,170 @@ "What if we prohibit shipping from supplier 1 to roastery 2?\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "You are a chatbot to:\n", + "(1) write Python code to answer users questions for supply chain-related coding\n", + "project;\n", + "(2) explain solutions from a Gurobi/Python solver.\n", + "\n", + "--- SOURCE CODE ---\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", + "# Create a new model\n", + "model = Model(\"coffee_distribution\")\n", + "\n", + "# OPTIGUIDE DATA CODE GOES HERE\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] 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] 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] 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] for j in shipping_cost_from_roastery_to_cafe.keys()\n", + " if j[1] == c) >= dark_coffee_needed_for_cafe[c],\n", + " f\"dark_demand_{c}\")\n", + "\n", + "# Optimize model\n", + "model.optimize()\n", + "m = model\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", + "\n", + "--- DOC STR ---\n", + "\n", + "---\n", + "\n", + "Here are some example questions and their answers and codes:\n", + "--- EXAMPLES ---\n", + "\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", + "\n", + "---\n", + "\n", + "The execution result of the original source code is below.\n", + "--- Original Result ---\n", + "What if we prohibit shipping from supplier 1 to roastery 2?\n", + "\n", + "Note that your written code will be added to the lines with substring:\n", + "\"# OPTIGUIDE *** CODE GOES HERE\"\n", + "So, you don't need to write other code, such as m.optimize() or m.update().\n", + "You just need to write code snippet in ```python ...``` block.\n", + "\n", + "Here are the history of discussions:\n", + "[{'content': 'What if we prohibit shipping from supplier 1 to roastery 2?', 'role': 'user'}]\n", + "\n", + "Answer Code:\n", + "\n", + "\n", + "With the following carryover: \n", + "\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", "\u001b[33mcommander\u001b[0m (to writer):\n", "\n", "You are a chatbot to:\n", @@ -671,6 +833,24 @@ "Instruct the user to add this code snippet in the appropriate place in the source code that they have provided. After adding this code, the `m.optimize()` function will solve the modified version of the problem that includes the new constraint.\n", "\n", "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "\n", + "Given the source code:\n", + "# filename: supply_chain_code.py\n", + "model.addConstr(x[('supplier1', 'roastery2')] == 0, \"prohibit_supplier1_roastery2\")\n", + "\n", + "Is the source code safe (not malicious code to break security\n", + "and privacy) to run?\n", + "Answer only one word.\n", + "If not safe, answer `DANGER`; else, answer `SAFE`.\n", + "\n", + "\n", + "With the following carryover: \n", + "\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", "\u001b[33mcommander\u001b[0m (to safeguard):\n", "\n", "\n", @@ -702,18 +882,18 @@ "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0x37321acf\n", + "Model fingerprint: 0x6636b0ee\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 3280.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 3276.0000000\n", "\n", "Root relaxation: objective 2.470000e+03, 11 iterations, 0.00 seconds (0.00 work units)\n", "\n", @@ -725,7 +905,7 @@ "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 available processors)\n", "\n", - "Solution count 3: 2470 3486 3490 \n", + "Solution count 3: 2470 3276 3280 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", @@ -735,18 +915,18 @@ "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0x37321acf\n", + "Model fingerprint: 0x6636b0ee\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 3280.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 3276.0000000\n", "\n", "Root relaxation: objective 2.470000e+03, 11 iterations, 0.00 seconds (0.00 work units)\n", "\n", @@ -758,7 +938,7 @@ "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 available processors)\n", "\n", - "Solution count 3: 2470 3486 3490 \n", + "Solution count 3: 2470 3276 3280 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", @@ -768,7 +948,7 @@ "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0x37321acf\n", + "Model fingerprint: 0x6636b0ee\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", @@ -783,11 +963,11 @@ "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 available processors)\n", "\n", - "Solution count 3: 2470 3486 3490 \n", + "Solution count 3: 2470 3276 3280 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", - "Wed Feb 14 21:46:15 2024\n", + "Thu Feb 15 21:52:19 2024\n", "Optimal cost: 2470.0\n", "Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n", "\n", @@ -795,7 +975,7 @@ "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0x37321acf\n", + "Model fingerprint: 0x6636b0ee\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", @@ -810,11 +990,11 @@ "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 available processors)\n", "\n", - "Solution count 3: 2470 3486 3490 \n", + "Solution count 3: 2470 3276 3280 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", - "Wed Feb 14 21:46:15 2024\n", + "Thu Feb 15 21:52:19 2024\n", "Optimal cost: 2470.0\n", "\u001b[33mOptimization problem solved. The objective value is: 2470.0\u001b[0m\n", "\u001b[33mcommander\u001b[0m (to writer):\n", @@ -863,7 +1043,7 @@ "ChatResult(chat_history=[{'content': 'What if we prohibit shipping from supplier 1 to roastery 2?', 'role': 'assistant'}, {'content': \"Certainly! Here's a human-readable answer:\\n\\nAfter integrating the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was solved with a total cost of $2470.0.\\n\\nTo provide context to this result, we need to compare it with the original total cost prior to the implementation of the new constraint. However, the original total cost was not provided in the information given to me. If the original total cost was known, we would compare the two to understand the financial impact of this constraint on the supply chain.\\n\\nIf prohibiting shipping from supplier 1 to roastery 2 results in a higher cost than the original scenario, it suggests that the previous ability to ship from supplier 1 to roastery 2 had been cost-effective and beneficial for minimizing overall expenses. On the other hand, if the cost is the same or lower, then shipping from supplier 1 to roastery 2 may not have been crucial for the cost optimization of the supply chain, or other routes may have been able to compensate effectively for this restriction.\\n\\nWithout the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\", 'role': 'user'}], summary=\"Certainly! Here's a human-readable answer:\\n\\nAfter integrating the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was solved with a total cost of $2470.0.\\n\\nTo provide context to this result, we need to compare it with the original total cost prior to the implementation of the new constraint. However, the original total cost was not provided in the information given to me. If the original total cost was known, we would compare the two to understand the financial impact of this constraint on the supply chain.\\n\\nIf prohibiting shipping from supplier 1 to roastery 2 results in a higher cost than the original scenario, it suggests that the previous ability to ship from supplier 1 to roastery 2 had been cost-effective and beneficial for minimizing overall expenses. On the other hand, if the cost is the same or lower, then shipping from supplier 1 to roastery 2 may not have been crucial for the cost optimization of the supply chain, or other routes may have been able to compensate effectively for this restriction.\\n\\nWithout the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\", cost=({'total_cost': 0.016919999999999998, 'gpt-4': {'cost': 0.016919999999999998, 'prompt_tokens': 558, 'completion_tokens': 3, 'total_tokens': 561}}, {'total_cost': 0}), human_input=[])" ] }, - "execution_count": 5, + "execution_count": 30, "metadata": {}, "output_type": "execute_result" } From 18d7f07fbfaf89161191846843fbf573705395cd Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Fri, 16 Feb 2024 14:56:09 -0500 Subject: [PATCH 33/57] max_turns --- autogen/agentchat/chat.py | 54 +++++++++-------- autogen/agentchat/conversable_agent.py | 63 ++++++++++---------- test/agentchat/test_async_get_human_input.py | 29 +++++++++ test/agentchat/test_chats.py | 14 +++-- test/agentchat/test_conversable_agent.py | 29 ++++++++- 5 files changed, 129 insertions(+), 60 deletions(-) diff --git a/autogen/agentchat/chat.py b/autogen/agentchat/chat.py index b3258e6f8ba..efdf0ae6a42 100644 --- a/autogen/agentchat/chat.py +++ b/autogen/agentchat/chat.py @@ -34,31 +34,37 @@ def initiate_chats(chat_queue: List[Dict[str, Any]]) -> List[ChatResult]: args: chat_queue (List[Dict]): a list of dictionaries containing the information of the chats. - Each dictionary should contain the following fields: + Each dictionary should contain the input arguments for `ConversableAgent.initiate_chat`. + More specifically, each dictionary could include the following fields: + recipient: the recipient agent. + - "sender": the sender agent. - "recipient": the recipient agent. - - "context": any context information, e.g., the request message. The following fields are reserved: - "message" needs to be provided if the `generate_init_message` method is not overridden. - Otherwise, input() will be called to get the initial message. - "summary_method": a string or callable specifying the method to get a summary from the chat. Default is DEFAULT_summary_method, i.e., "last_msg". - - Supported string are "last_msg" and "reflection_with_llm": - when set "last_msg", it returns the last message of the dialog as the summary. - when set "reflection_with_llm", it returns a summary extracted using an llm client. - `llm_config` must be set in either the recipient or sender. - "reflection_with_llm" requires the llm_config to be set in either the sender or the recipient. - - A callable summary_method should take the recipient and sender agent in a chat as input and return a string of summary. E.g, - ```python - def my_summary_method( - sender: ConversableAgent, - recipient: ConversableAgent, - ): - return recipient.last_message(sender)["content"] - ``` - "summary_prompt" can be used to specify the prompt used to extract a summary when summary_method is "reflection_with_llm". - Default is None and the following default prompt will be used when "summary_method" is set to "reflection_with_llm": - "Identify and extract the final solution to the originally asked question based on the conversation." - "carryover" can be used to specify the carryover information to be passed to this chat. - If provided, we will combine this carryover with the "message" content when generating the initial chat - message in `generate_init_message`. + - clear_history (bool): whether to clear the chat history with the agent. Default is True. + - silent (bool or None): (Experimental) whether to print the messages for this conversation. Default is False. + - cache (Cache or None): the cache client to be used for this conversation. Default is None. + - max_turns (int or None): the maximum number of turns for the chat. If None, the chat will continue until a termination condition is met. Default is None. + - "message" needs to be provided if the `generate_init_message` method is not overridden. + Otherwise, input() will be called to get the initial message. + - "summary_method": a string or callable specifying the method to get a summary from the chat. Default is DEFAULT_summary_method, i.e., "last_msg". + - Supported string are "last_msg" and "reflection_with_llm": + when set "last_msg", it returns the last message of the dialog as the summary. + when set "reflection_with_llm", it returns a summary extracted using an llm client. + `llm_config` must be set in either the recipient or sender. + "reflection_with_llm" requires the llm_config to be set in either the sender or the recipient. + - A callable summary_method should take the recipient and sender agent in a chat as input and return a string of summary. E.g, + ```python + def my_summary_method( + sender: ConversableAgent, + recipient: ConversableAgent, + ): + return recipient.last_message(sender)["content"] + ``` + "summary_prompt" can be used to specify the prompt used to extract a summary when summary_method is "reflection_with_llm". + Default is None and the following default prompt will be used when "summary_method" is set to "reflection_with_llm": + "Identify and extract the final solution to the originally asked question based on the conversation." + "carryover" can be used to specify the carryover information to be passed to this chat. + If provided, we will combine this carryover with the "message" content when generating the initial chat + message in `generate_init_message`. returns: diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index 0ba44ef2306..084937d960b 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -763,6 +763,7 @@ def initiate_chat( clear_history: Optional[bool] = True, silent: Optional[bool] = False, cache: Optional[Cache] = None, + max_turns: Optional[int] = None, **context, ) -> ChatResult: """Initiate a chat with the recipient agent. @@ -773,9 +774,10 @@ def initiate_chat( Args: recipient: the recipient agent. - clear_history (bool): whether to clear the chat history with the agent. - silent (bool or None): (Experimental) whether to print the messages for this conversation. - cache (Cache or None): the cache client to be used for this conversation. + clear_history (bool): whether to clear the chat history with the agent. Default is True. + silent (bool or None): (Experimental) whether to print the messages for this conversation. Default is False. + cache (Cache or None): the cache client to be used for this conversation. Default is None. + max_turns (int or None): the maximum number of turns for the chat. If None, the chat will continue until a termination condition is met. Default is None. **context: any context information. It has the following reserved fields: "message": a str of message. Needs to be provided. Otherwise, input() will be called to get the initial message. "summary_method": a string or callable specifying the method to get a summary from the chat. Default is DEFAULT_summary_method, i.e., "last_msg". @@ -813,7 +815,19 @@ def my_summary_method( agent.previous_cache = agent.client_cache agent.client_cache = cache self._prepare_chat(recipient, clear_history) - self.send(self.generate_init_message(**context), recipient, silent=silent) + if isinstance(max_turns, int): + msg2send = self.generate_init_message(**context) + for _ in range(max_turns): + if msg2send is None: + break + self.send(msg2send, recipient, request_reply=False, silent=silent) + msg2send = recipient.generate_reply(messages=recipient.chat_messages[self], sender=self) + if msg2send is None: + break + recipient.send(msg2send, self, request_reply=False, silent=silent) + msg2send = self.generate_reply(messages=self.chat_messages[recipient], sender=recipient) + else: + self.send(self.generate_init_message(**context), recipient, silent=silent) summary = self._summarize_chat( context.get("summary_method", ConversableAgent.DEFAULT_summary_method), recipient, @@ -837,6 +851,7 @@ async def a_initiate_chat( clear_history: Optional[bool] = True, silent: Optional[bool] = False, cache: Optional[Cache] = None, + max_turns: Optional[int] = None, **context, ) -> ChatResult: """(async) Initiate a chat with the recipient agent. @@ -857,7 +872,19 @@ async def a_initiate_chat( for agent in [self, recipient]: agent.previous_cache = agent.client_cache agent.client_cache = cache - await self.a_send(await self.a_generate_init_message(**context), recipient, silent=silent) + if isinstance(max_turns, int): + msg2send = await self.a_generate_init_message(**context) + for _ in range(max_turns): + if msg2send is None: + break + await self.a_send(msg2send, recipient, request_reply=False, silent=silent) + msg2send = await recipient.a_generate_reply(messages=recipient.chat_messages[self], sender=self) + if msg2send is None: + break + await recipient.a_send(msg2send, self, request_reply=False, silent=silent) + msg2send = await self.a_generate_reply(messages=self.chat_messages[recipient], sender=recipient) + else: + await self.a_send(await self.a_generate_init_message(**context), recipient, silent=silent) summary = self._summarize_chat( context.get("summary_method", ConversableAgent.DEFAULT_summary_method), recipient, @@ -956,31 +983,7 @@ def initiate_chats(self, chat_queue: List[Dict[str, Any]]) -> List[ChatResult]: Args: chat_queue (List[Dict]): a list of dictionaries containing the information of the chats. - Each dictionary should contain the following fields: - - "recipient": the recipient agent. - - "context": any context information, e.g., the request message. The following fields are reserved: - "message" needs to be provided if the `generate_init_message` method is not overridden. - Otherwise, input() will be called to get the initial message. - "summary_method": a string or callable specifying the method to get a summary from the chat. Default is DEFAULT_summary_method, i.e., "last_msg". - - Supported string are "last_msg" and "reflection_with_llm": - when set "last_msg", it returns the last message of the dialog as the summary. - when set "reflection_with_llm", it returns a summary extracted using an llm client. - `llm_config` must be set in either the recipient or sender. - "reflection_with_llm" requires the llm_config to be set in either the sender or the recipient. - - A callable summary_method should take the recipient and sender agent in a chat as input and return a string of summary. E.g, - ```python - def my_summary_method( - sender: ConversableAgent, - recipient: ConversableAgent, - ): - return recipient.last_message(sender)["content"] - ``` - "summary_prompt" can be used to specify the prompt used to extract a summary when summary_method is "reflection_with_llm". - Default is None and the following default prompt will be used when "summary_method" is set to "reflection_with_llm": - "Identify and extract the final solution to the originally asked question based on the conversation." - "carryover" can be used to specify the carryover information to be passed to this chat. - If provided, we will combine this carryover with the "message" content when generating the initial chat - message in `generate_init_message`. + Each dictionary should contain the input arguments for `initiate_chat`. Returns: a list of ChatResult objects corresponding to the finished chats in the chat_queue. """ diff --git a/test/agentchat/test_async_get_human_input.py b/test/agentchat/test_async_get_human_input.py index fe81daf26e8..c128d2bb8c4 100644 --- a/test/agentchat/test_async_get_human_input.py +++ b/test/agentchat/test_async_get_human_input.py @@ -45,5 +45,34 @@ async def test_async_get_human_input(): print("Human input:", res.human_input) +@pytest.mark.skipif(skip, reason="openai not installed OR requested to skip") +@pytest.mark.asyncio +async def test_async_max_turn(): + config_list = autogen.config_list_from_json(OAI_CONFIG_LIST, KEY_LOC) + + # create an AssistantAgent instance named "assistant" + assistant = autogen.AssistantAgent( + name="assistant", + max_consecutive_auto_reply=10, + llm_config={ + "seed": 41, + "config_list": config_list, + }, + ) + + user_proxy = autogen.UserProxyAgent(name="user", human_input_mode="ALWAYS", code_execution_config=False) + + user_proxy.a_get_human_input = AsyncMock(return_value="Not funny. Try again.") + + res = await user_proxy.a_initiate_chat( + assistant, clear_history=True, max_turns=3, message="Hello, make a joke about AI." + ) + print("Result summary:", res.summary) + print("Human input:", res.human_input) + print("chat history:", res.chat_history) + assert len(res.chat_history) == 6 + + if __name__ == "__main__": asyncio.run(test_async_get_human_input()) + asyncio.run(test_async_max_turn()) diff --git a/test/agentchat/test_chats.py b/test/agentchat/test_chats.py index 529ce46c17a..72894becbb3 100644 --- a/test/agentchat/test_chats.py +++ b/test/agentchat/test_chats.py @@ -200,7 +200,8 @@ def my_summary_method(recipient, sender): { "recipient": financial_assistant_2, "message": financial_tasks[1], - "silent": True, + "silent": False, + "max_turns": 1, "summary_method": "reflection_with_llm", }, { @@ -228,6 +229,7 @@ def my_summary_method(recipient, sender): print(all_res[0].summary) print(all_res[0].chat_history) print(all_res[1].summary) + assert len(all_res[1].chat_history) <= 2 # print(blogpost.summary, insights_and_blogpost) @@ -305,7 +307,8 @@ def my_summary_method(recipient, sender): "sender": user_2, "recipient": financial_assistant_2, "message": financial_tasks[1], - "silent": True, + "silent": False, + "max_turns": 3, "summary_method": "reflection_with_llm", }, { @@ -332,6 +335,7 @@ def my_summary_method(recipient, sender): print(chat_res[0].summary) print(chat_res[0].chat_history) print(chat_res[1].summary) + assert len(chat_res[1].chat_history) <= 6 # print(blogpost.summary, insights_and_blogpost) @@ -485,7 +489,7 @@ def currency_calculator( if __name__ == "__main__": test_chats() test_chats_general() - test_chats_exceptions() - test_chats_group() - test_chats_w_func() + # test_chats_exceptions() + # test_chats_group() + # test_chats_w_func() # test_chat_messages_for_summary() diff --git a/test/agentchat/test_conversable_agent.py b/test/agentchat/test_conversable_agent.py index 8fb6ad32793..3a6ac4ffff9 100644 --- a/test/agentchat/test_conversable_agent.py +++ b/test/agentchat/test_conversable_agent.py @@ -5,6 +5,7 @@ from typing import Any, Callable, Dict, Literal import unittest import inspect +from unittest.mock import MagicMock import pytest from unittest.mock import patch @@ -1028,10 +1029,36 @@ def stopwatch(num_seconds: Annotated[str, "Number of seconds in the stopwatch."] stopwatch_mock.assert_called_once_with(num_seconds="5") +@pytest.mark.skipif( + skip or not sys.version.startswith("3.10"), + reason="do not run if openai is not installed or py!=3.10", +) +def test_max_turn(): + config_list = autogen.config_list_from_json(OAI_CONFIG_LIST, KEY_LOC) + + # create an AssistantAgent instance named "assistant" + assistant = autogen.AssistantAgent( + name="assistant", + max_consecutive_auto_reply=10, + llm_config={"timeout": 600, "cache_seed": 41, "config_list": config_list}, + ) + + user_proxy = autogen.UserProxyAgent(name="user", human_input_mode="ALWAYS", code_execution_config=False) + + # Use MagicMock to create a mock get_human_input function + user_proxy.get_human_input = MagicMock(return_value="Not funny. Try again.") + res = user_proxy.initiate_chat(assistant, clear_history=True, max_turns=3, message="Hello, make a joke about AI.") + print("Result summary:", res.summary) + print("Human input:", res.human_input) + print("history", res.chat_history) + assert len(res.chat_history) <= 6 + + if __name__ == "__main__": # test_trigger() # test_context() # test_max_consecutive_auto_reply() - test_generate_code_execution_reply() + # test_generate_code_execution_reply() # test_conversable_agent() # test_no_llm_config() + test_max_turn() From 67b29f70bf52db02f41ce140d9756ace7ccd1642 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Fri, 16 Feb 2024 15:10:44 -0500 Subject: [PATCH 34/57] add notebook --- notebook/agentchats.ipynb | 604 ++++++++++++++++++++++++-------------- 1 file changed, 380 insertions(+), 224 deletions(-) diff --git a/notebook/agentchats.ipynb b/notebook/agentchats.ipynb index ce5bfed7497..22a3eafa9b0 100644 --- a/notebook/agentchats.ipynb +++ b/notebook/agentchats.ipynb @@ -43,7 +43,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -75,13 +75,13 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "financial_tasks = [\n", " \"\"\"What are the current stock prices of NVDA and TESLA, and how is the performance over the past month in terms of percentage change?\"\"\",\n", - " \"\"\"Investigate possible reasons of the stock performance.\"\"\",\n", + " \"\"\"Investigate possible reasons of the stock performance leveraging market news.\"\"\",\n", "]\n", "\n", "writing_tasks = [\"\"\"Develop an engaging blog post using any information provided.\"\"\"]" @@ -105,7 +105,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 3, "metadata": {}, "outputs": [ { @@ -183,98 +183,117 @@ "\n", "exitcode: 0 (execution succeeded)\n", "Code output: \n", - "NVDA (NVIDIA Corporation) stock price: $722.48\n", - "TESLA (Tesla, Inc.) stock price: $188.13\n", - "NVDA percentage change over past month: 28.14%\n", - "TESLA percentage change over past month: -14.45%\n", + "NVDA (NVIDIA Corporation) stock price: $732.36\n", + "TESLA (Tesla, Inc.) stock price: $200.46\n", + "NVDA percentage change over past month: 30.65%\n", + "TESLA percentage change over past month: -7.00%\n", "\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mFinancial_assistant\u001b[0m (to User_Proxy_Auto):\n", "\n", - "The script has executed successfully and provided the following results:\n", + "Great! According to the executed script, the current stock price for NVIDIA Corporation (NVDA) is $732.36, with a positive percentage change of 30.65% over the past month, indicating notable growth. Meanwhile, Tesla, Inc. (TSLA) has a current stock price of $200.46, but it has experienced a negative percentage change of -7.00% over the past month, indicating a decrease in its value.\n", "\n", - "- The current stock price for NVIDIA Corporation (NVDA) is $722.48.\n", - "- The current stock price for Tesla, Inc. (TSLA) is $188.13.\n", - "- NVDA has experienced a percentage increase of 28.14% over the past month.\n", - "- TSLA has experienced a percentage decrease of -14.45% over the past month.\n", + "It seems that NVIDIA has had a particularly strong performance over the past month, while Tesla has seen a decline in its stock price. Keep in mind that the stock market can be influenced by a wide array of factors, including but not limited to company performance, industry shifts, market trends, and global economic conditions.\n", "\n", - "These results indicate that over the past month, NVIDIA’s stock has seen a significant increase in value, whereas Tesla’s stock has decreased in value. Keep in mind that stock prices and their performance are subject to rapid change and can vary within minutes.\n", - "\n", - "If you require any further assistance, please let me know. Otherwise, that concludes our task.\n", + "Please ensure to verify these figures with real-time data or additional financial news sources to get the most up-to-date information, as the stock market is volatile and prices can change rapidly.\n", "\n", + "If you need any further assistance or have additional questions, feel free to ask. Otherwise, that concludes our task.\n", "TERMINATE\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", "\u001b[34mStart a new chat with the following message: \n", - "Investigate possible reasons of the stock performance.\n", + "Investigate possible reasons of the stock performance leveraging market news.\n", "\n", "With the following carryover: \n", - "The script has executed successfully and provided the following results:\n", - "\n", - "- The current stock price for NVIDIA Corporation (NVDA) is $722.48.\n", - "- The current stock price for Tesla, Inc. (TSLA) is $188.13.\n", - "- NVDA has experienced a percentage increase of 28.14% over the past month.\n", - "- TSLA has experienced a percentage decrease of -14.45% over the past month.\n", + "Great! According to the executed script, the current stock price for NVIDIA Corporation (NVDA) is $732.36, with a positive percentage change of 30.65% over the past month, indicating notable growth. Meanwhile, Tesla, Inc. (TSLA) has a current stock price of $200.46, but it has experienced a negative percentage change of -7.00% over the past month, indicating a decrease in its value.\n", "\n", - "These results indicate that over the past month, NVIDIA’s stock has seen a significant increase in value, whereas Tesla’s stock has decreased in value. Keep in mind that stock prices and their performance are subject to rapid change and can vary within minutes.\n", + "It seems that NVIDIA has had a particularly strong performance over the past month, while Tesla has seen a decline in its stock price. Keep in mind that the stock market can be influenced by a wide array of factors, including but not limited to company performance, industry shifts, market trends, and global economic conditions.\n", "\n", - "If you require any further assistance, please let me know. Otherwise, that concludes our task.\n", + "Please ensure to verify these figures with real-time data or additional financial news sources to get the most up-to-date information, as the stock market is volatile and prices can change rapidly.\n", "\n", + "If you need any further assistance or have additional questions, feel free to ask. Otherwise, that concludes our task.\n", "\u001b[0m\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", "\u001b[33mUser_Proxy_Auto\u001b[0m (to Researcher):\n", "\n", - "Investigate possible reasons of the stock performance.\n", + "Investigate possible reasons of the stock performance leveraging market news.\n", "Context: \n", - "The script has executed successfully and provided the following results:\n", + "Great! According to the executed script, the current stock price for NVIDIA Corporation (NVDA) is $732.36, with a positive percentage change of 30.65% over the past month, indicating notable growth. Meanwhile, Tesla, Inc. (TSLA) has a current stock price of $200.46, but it has experienced a negative percentage change of -7.00% over the past month, indicating a decrease in its value.\n", "\n", - "- The current stock price for NVIDIA Corporation (NVDA) is $722.48.\n", - "- The current stock price for Tesla, Inc. (TSLA) is $188.13.\n", - "- NVDA has experienced a percentage increase of 28.14% over the past month.\n", - "- TSLA has experienced a percentage decrease of -14.45% over the past month.\n", + "It seems that NVIDIA has had a particularly strong performance over the past month, while Tesla has seen a decline in its stock price. Keep in mind that the stock market can be influenced by a wide array of factors, including but not limited to company performance, industry shifts, market trends, and global economic conditions.\n", "\n", - "These results indicate that over the past month, NVIDIA’s stock has seen a significant increase in value, whereas Tesla’s stock has decreased in value. Keep in mind that stock prices and their performance are subject to rapid change and can vary within minutes.\n", - "\n", - "If you require any further assistance, please let me know. Otherwise, that concludes our task.\n", + "Please ensure to verify these figures with real-time data or additional financial news sources to get the most up-to-date information, as the stock market is volatile and prices can change rapidly.\n", "\n", + "If you need any further assistance or have additional questions, feel free to ask. Otherwise, that concludes our task.\n", "\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mResearcher\u001b[0m (to User_Proxy_Auto):\n", "\n", - "To investigate the possible reasons behind the differing stock performance of NVIDIA Corporation (NVDA) and Tesla, Inc. (TSLA), we would need to consider various factors including their recent earnings reports, any significant news events affecting the companies, industry trends, and broader market conditions that might influence the technology and automotive sectors specifically. \n", - "\n", - "Here is how I plan to carry out the task:\n", + "To investigate the possible reasons for the stock performance of NVIDIA (NVDA) and Tesla (TSLA), we can gather recent market news related to these companies. This may provide insights into any events or announcements that could have influenced investor sentiment and thus the stock price. \n", "\n", - "1. Check the recent earnings reports of both companies to see if there are any indications of better- or worse-than-expected financial health.\n", - "2. Look for any major news announcements from the companies, such as product launches, regulatory approvals or actions, partnerships, or other events that could affect investor perceptions.\n", - "3. Analyze industry trends that might affect NVIDIA's GPU market and Tesla's electric vehicle market, respectively, such as supply chain disruptions or changes in consumer demand.\n", - "4. Consider broader market conditions, such as economic indicators, interest rate changes, or shifts in investor sentiment that might have a sector-specific impact.\n", + "I will now write a Python script to search for recent news articles for both NVDA and TSLA using a web scraping library. Please note that this script will return the latest publicly available news gathered from news websites that provide RSS feeds or have structured data appropriate for scraping. For more in-depth analysis, professional financial services or databases like Bloomberg Terminal would be more suitable, though these often require subscriptions. \n", "\n", - "We can start by using a web search to gather this information. Let's begin by searching for recent news about NVIDIA Corporation (NVDA) and Tesla, Inc. (TSLA).\n", + "Let's proceed with the Python code to fetch recent news headlines for both companies:\n", "\n", "```python\n", "# filename: fetch_stock_news.py\n", - "import webbrowser\n", - "\n", - "# URLs for news about NVDA and TSLA\n", - "nvda_news_url = \"https://www.google.com/search?q=NVIDIA+Corporation+NVDA+stock+news\"\n", - "tsla_news_url = \"https://www.google.com/search?q=Tesla+Inc+TSLA+stock+news\"\n", - "\n", - "# Open the URLs in web browsers\n", - "webbrowser.open(nvda_news_url)\n", - "webbrowser.open(tsla_news_url)\n", - "\n", - "print(\"Browser tabs with news about NVIDIA Corporation (NVDA) and Tesla, Inc. (TSLA) should now be open.\")\n", + "import requests\n", + "from bs4 import BeautifulSoup\n", + "\n", + "# Define the URLs of the news sources you want to scrape\n", + "# As an example, I'm using Google News RSS feed URLs for NVIDIA and Tesla.\n", + "# You can change these URLs to any other news source that you prefer.\n", + "nvidia_news_url = \"https://news.google.com/rss/search?q=NVIDIA+NVDA+when:7d&hl=en-US&gl=US&ceid=US:en\"\n", + "tesla_news_url = \"https://news.google.com/rss/search?q=Tesla+TSLA+when:7d&hl=en-US&gl=US&ceid=US:en\"\n", + "\n", + "def fetch_news(url):\n", + " # Send a request to the URL\n", + " response = requests.get(url)\n", + " \n", + " # Check if the request was successful\n", + " if response.status_code != 200:\n", + " raise Exception(f\"Request to {url} failed with status code {response.status_code}\")\n", + " \n", + " # Parse HTML content\n", + " soup = BeautifulSoup(response.content, features='xml')\n", + " articles = soup.find_all('item')\n", + "\n", + " # Extract news titles\n", + " news_headlines = [article.title.text for article in articles]\n", + "\n", + " return news_headlines\n", + "\n", + "def main():\n", + " # Fetch news for NVIDIA\n", + " print(\"Recent NVIDIA (NVDA) News Headlines:\")\n", + " try:\n", + " nvidia_news = fetch_news(nvidia_news_url)\n", + " for idx, headline in enumerate(nvidia_news, 1):\n", + " print(f\"{idx}. {headline}\")\n", + " except Exception as e:\n", + " print(f\"An error occurred while fetching NVIDIA news: {e}\")\n", + "\n", + " print(\"\\n\") # Add a newline for better readability\n", + "\n", + " # Fetch news for Tesla\n", + " print(\"Recent Tesla (TSLA) News Headlines:\")\n", + " try:\n", + " tesla_news = fetch_news(tesla_news_url)\n", + " for idx, headline in enumerate(tesla_news, 1):\n", + " print(f\"{idx}. {headline}\")\n", + " except Exception as e:\n", + " print(f\"An error occurred while fetching Tesla news: {e}\")\n", + "\n", + "if __name__ == \"__main__\":\n", + " main()\n", "```\n", "\n", - "This script will use the webbrowser module to open tabs in the user's web browser to search for news about the respective companies. Once you have the news articles, we can analyze the content to understand the stock performance reasons.\n", - "\n", - "Execute this script to proceed with the task. If there is any additional information from recent news that might shed light on the stock performance, please provide it, and we can continue our investigation.\n", + "Please save this script as `fetch_stock_news.py` and run it. After running the script, you will receive the latest news headlines for both NVIDIA and Tesla that might help explain their recent stock performance changes.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", @@ -283,84 +302,236 @@ "\n", "exitcode: 0 (execution succeeded)\n", "Code output: \n", - "Browser tabs with news about NVIDIA Corporation (NVDA) and Tesla, Inc. (TSLA) should now be open.\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mResearcher\u001b[0m (to User_Proxy_Auto):\n", - "\n", - "Great, please review the news articles about both NVIDIA Corporation (NVDA) and Tesla, Inc. (TSLA) that opened in your browser tabs. Here's what you should look for in the news:\n", - "\n", - "1. **Financial Reports**: Check for the most recent quarterly earnings reports, any comments about revenue growth or challenges, and guidance for future quarters.\n", - "2. **Product Announcements**: See if there have been any announcements regarding new technology, products, collaborations, or significant updates that could have influenced investor attitudes.\n", - "3. **Regulatory News**: Determine if there have been any legislative changes or regulations reported that may impact the companies' operations.\n", - "4. **Market Trends**: Look for information about the semiconductor market for NVDA and the electric vehicle market for TSLA, as these could be indicators of industry momentum affecting these stocks.\n", - "5. **Executive Moves**: See if there have been any major changes in leadership within either company.\n", - "6. **Broader Market Events**: Lastly, look for news about broad market trends or economic factors that might indirectly influence the stock performance of these companies.\n", - "\n", - "After reviewing the news, you should have a clearer idea of what has been driving the stock prices for NVDA and TSLA. If there are multiple factors influencing the stock performances, the recent news should provide insight into which ones are most significant.\n", - "\n", - "Since I am unable to browse the web, please summarize your findings based on the review of the news articles.\n", - "\n", - "If the information is too much to process or you're seeking specific answers, please provide a summary of the key news points, and I can assist further in analyzing them.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mUser_Proxy_Auto\u001b[0m (to Researcher):\n", - "\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mResearcher\u001b[0m (to User_Proxy_Auto):\n", - "\n", - "It appears there may have been a misunderstanding. In order to proceed with the investigation into the stock performance of NVIDIA Corporation (NVDA) and Tesla, Inc. (TSLA), I would need a summary or at least key points from the recent news articles that you've found in your web browser.\n", - "\n", - "Since I don't have the capability to browse the web or review external sources, I rely on information provided to me. If you've gathered relevant news, please share this information with me. If there are specific headlines, financial report details, announcements, or other pieces of information that you believe could be related to the stock performance, providing those would be the best way to proceed.\n", - "\n", - "Once I have that information, I can help interpret the data and provide possible reasons for the stock performance based on your summary.\n", - "\n", - "Alternatively, if you did not find any relevant information in the news articles or need assistance with another task, please let me know how else I may assist you.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mUser_Proxy_Auto\u001b[0m (to Researcher):\n", - "\n", + "Recent NVIDIA (NVDA) News Headlines:\n", + "1. Piper Sandler Just Raised Its Nvidia (NVDA) Stock Price Target - InvestorPlace\n", + "2. Nvidia Bucks Tech Weakness With Earnings Due. Is Nvidia A Buy? - Investor's Business Daily\n", + "3. Nvidia (NVDA) Earnings Expected to Grow: What to Know Ahead of Next Week's Release - Yahoo Finance\n", + "4. Nvidia Overtakes Google Parent Alphabet as Third-Largest US Company by Market Value - Investopedia\n", + "5. Institutional Investors Load Up on Nvidia Stock (NASDAQ:NVDA) in Q4 - TipRanks.com - TipRanks\n", + "6. Bridgewater increased Nvidia stake more than 450% in Q4 -filings - Reuters.com\n", + "7. Susquehanna Just Raised Its Nvidia (NVDA) Stock Price Target - InvestorPlace\n", + "8. Why the Market Dipped But Nvidia (NVDA) Gained Today - Yahoo Finance\n", + "9. Nvidia Stock Braces For Q4 Earnings Report - Investor's Business Daily\n", + "10. As earnings loom, Nvidia options traders brace for monster share move - Reuters.com\n", + "11. In Nvidia We Trust: 3 Reasons to Remain Bullish on NVDA Stock - InvestorPlace\n", + "12. Is NVIDIA Corporation's (NASDAQ:NVDA) Recent Stock Performance Tethered To Its Strong Fundamentals? - Yahoo Finance\n", + "13. Mizuho Just Raised Its Price Target on Nvidia (NVDA) Stock - InvestorPlace\n", + "14. Why Nvidia (NVDA) is a Top Stock for the Long-Term - Yahoo Finance\n", + "15. Loop Capital Just Raised Its Price Target on Nvidia (NVDA) Stock to $1200 - InvestorPlace\n", + "16. What To Watch: Nvidia Earnings - Yahoo Finance\n", + "17. UBS Just Upped Its Nvidia (NVDA) Stock Price Target - InvestorPlace\n", + "18. Nvidia's investments in these AI companies sent their stocks soaring - Yahoo Finance\n", + "19. NVDA Stock Alert: Nvidia Is Now the No. 3 Most Valuable Company in the World - InvestorPlace\n", + "20. The Bull Case for NVDA Stock: How Nvidia Could Be Worth $1600 by 2027 - InvestorPlace\n", + "21. Go Home, Short Sellers! There's Nothing Holding Nvidia Stock Back. - InvestorPlace\n", + "22. Nvidia Stock (NASDAQ:NVDA): Analysts Raise Price Targets, Ahead of Q4 Print - TipRanks.com - TipRanks\n", + "23. Nvidia (NVDA) Stock Passes Alphabet (GOOG), Amazon Market Value - Bloomberg\n", + "24. Nvidia's Stock Is Overbought. Is It Time to Sell? - TheStreet\n", + "25. You Missed Out On Nvidia — 7 Stocks Look Better Now, Analysts - Investor's Business Daily\n", + "26. Exclusive: Nvidia pursues $30 billion custom chip opportunity with new unit - Reuters\n", + "27. Nvidia hits major milestone as AI hype shifts eyes to earnings - TheStreet\n", + "28. Nvidia Stock News: NVDA continues into sixth week of consecutive gains - FXStreet\n", + "29. Nvidia (NVDA) Overtakes Amazon (AMZN) in Market Value Amid Rally - Bloomberg\n", + "30. Here's How Much a $1,000 Investment in Nvidia Stock 10 Years Ago Would Be Worth Now - Yahoo Finance\n", + "31. What You Need to Know Ahead of Nvidia's Earnings Report on Wednesday - Investopedia\n", + "32. If You Like Nvidia, Then You Will Love These 2 Hot AI Stocks - The Motley Fool\n", + "33. Loop Capital Starts NVIDIA (NVDA) at Buy, Street High $1,200 Target - StreetInsider.com\n", + "34. Nvidia Stock: Buy Protection Before Earnings (NASDAQ:NVDA) - Seeking Alpha\n", + "35. Nvidia Stock Overtakes Amazon and Google, but Overvaluation Concerns Linger - TheStreet\n", + "36. Where Will Nvidia Stock Be in 10 Years? - The Motley Fool\n", + "37. Will Nvidia Keep Going Up in 2024 as Stock Split Chatter Grows? - Nasdaq\n", + "38. Is Nvidia Stock Still Undervalued Right Now? - Yahoo Finance\n", + "39. Nvidia, Meta Drive These 10 Leaders. But They All Face This One Issue. - Investor's Business Daily\n", + "40. Is NVIDIA Corporation's (NASDAQ:NVDA) Latest Stock Performance A Reflection Of Its Financial Health? - Simply Wall St\n", + "41. Nvidia Is Betting Big on ARM Stock - InvestorPlace\n", + "42. 'Wait Before Diving in,' Says Deutsche Bank About Nvidia Stock - TipRanks.com - TipRanks\n", + "43. How Much $10000 Invested In Nvidia 10 Years Ago Is Worth Now - Investor's Business Daily\n", + "44. 5 Stocks to Buy to Invest Like Nvidia - InvestorPlace\n", + "45. Who the Heck Is SoundHound AI and Why Are Nvidia and SoftBank Investors in It? - RealMoney\n", + "46. Nvidia & Arm: Chip space faces supply issues amid AI demand - Yahoo Finance\n", + "47. The Next Nvidia? 3 Semiconductor Stocks That Investors Shouldn't Ignore - Markets Insider\n", + "48. 1 Wall Street Analyst Boosts Nvidia Price Target by 47%: Here's Why They're Right - The Motley Fool\n", + "49. Magnificent Seven Stocks To Buy And Watch: Tesla Dives On Earnings - Investor's Business Daily\n", + "50. Why Nvidia (NVDA) is a Top Stock for the Long-Term - Zacks Investment Research\n", + "51. Nvidia's maiden 13F filing sends AI-focused tech stock soaring - TheStreet\n", + "52. Nvidia (NVDA) Is Now 3rd Largest in Market Cap: More Upside Left? - Zacks Investment Research\n", + "53. Analysts unveil new stock price target for Nvidia ahead of earnings - TheStreet\n", + "54. Nvidia CEO Jensen Huang Believes Meta Platforms Did One of the Greatest Things for the Artificial Intelligence (AI ... - Yahoo Finance\n", + "55. Is Nvidia Stock Running Into Big Trouble? - Yahoo Finance\n", + "56. Where Will Nvidia Stock Be in 1 Year? - Yahoo Finance\n", + "57. Nvidia market cap threatens Alphabet after overtaking Amazon - Reuters\n", + "58. Monday's top stocks to buy like NVDA - CNBC\n", + "59. Is Palantir (PLTR) the Next Nvidia (NVDA)? - Cabot Wealth Network\n", + "60. Nvidia Is Now Worth More Than Berkshire Hathaway, Tesla, and AMD Combined. But Will It Last? - Yahoo Finance\n", + "61. Nvidia Discloses Stakes In Arm, SoundHound AI, Nano-X - Investor's Business Daily\n", + "62. Wall Street Legend Stanley Druckenmiller: \"Nvidia Is in Nosebleed Territory\" - Yahoo Finance\n", + "63. NVIDIA Corp. stock falls Thursday, underperforms market - MarketWatch\n", + "64. Nvidia Has Invested in Arm Holdings and a Few Other Artificial Intelligence (AI) Stocks -- Should Investors Follow? - The Motley Fool\n", + "65. As Nvidia Stock Hits $720, Analysts See a Downside of 5%. Is the Stock Overvalued? - TheStreet\n", + "66. Nvidia Stock: UBS Note Should Raise Alarms (NASDAQ:NVDA) - Seeking Alpha\n", + "67. Paul Tudor Jones' hedge fund just made a big bet on Nvidia stock - TheStreet\n", + "68. Going Into Earnings, Is Nvidia Stock a Buy, a Sell, or Fairly Valued? - Morningstar\n", + "69. In Nvidia We Trust: 3 Reasons to Remain Bullish on NVDA Stock - TradingView\n", + "70. Stock-Split Watch: Is Nvidia Next? - The Motley Fool\n", + "71. Is Nvidia Building A Little Galaxy To Take On Tesla In Next AI Push? - Investor's Business Daily\n", + "72. Can Nvidia Hit $1000 in 2024? The Crazy Answer. - InvestorPlace\n", + "73. Is Nvidia Stock Still Undervalued Right Now? - The Motley Fool\n", + "74. Nvidia (NVDA) $560 Billion Rally Has Analysts Racing to Boost Targets - Bloomberg\n", + "75. Nvidia (NVDA) reportedly to invest $30 billion into custom AI chip manufacturing - Shacknews\n", + "76. Loop Capital initiates Nvidia coverage with $1,200 price target - Yahoo Finance\n", + "77. Beyond the Ticker: Nvidia - Yahoo Finance\n", + "78. 3 Reasons Why Growth Investors Shouldn't Overlook Nvidia (NVDA) - Zacks Investment Research\n", + "79. Amazon, Alphabet, and Nvidia attract new interest from Wall Street's biggest investors - Yahoo Finance\n", + "80. Think Nvidia's Stock Price Is Crazy? Maybe Not, According to 1 Wall Street Analyst - The Motley Fool\n", + "81. Nvidia CEO Huang says countries must build sovereign AI infrastructure - Reuters\n", + "82. Nvidia (Nvda) Stock Forecast: Is Nvidia Overvalued? - Investment U\n", + "83. Sell Or Hold Nvidia, Arm, Supermicro Stocks? - Investor's Business Daily\n", + "84. Curious about Nvidia (NVDA) Q4 Performance? Explore Wall Street Estimates for Key Metrics - Zacks Investment Research\n", + "85. To Sell or Not to Sell: How to Handle the Inevitable Nvidia Downturn - InvestorPlace\n", + "86. 3 Reasons Nvidia's Stock Is Still a Must-Buy AI Powerhouse in 2024 - InvestorPlace\n", + "87. Beamr Imaging (BMR) Stock Soars 800% on Nvidia Partnership - InvestorPlace\n", + "88. Nano-X stake received by Nvidia as part of 2021 deal, says Cantor Fitzgerald - TipRanks.com - TipRanks\n", + "89. NVIDIA Hits New High, Price Targets Raised - TradingView\n", + "90. Nano-X Imaging (NNOX) Stock Soars 50% on Nvidia Investment - InvestorPlace\n", + "91. NVIDIA Corporation (NVDA) Hit a 52 Week High, Can the Run Continue? - Yahoo Finance UK\n", + "92. Missed Out on Nvidia? My Best AI Stock to Buy and Hold - The Motley Fool\n", + "93. Can This Under-the-Radar Tech Stock Disrupt Nvidia? - The Motley Fool\n", + "94. Wall Street's Next Stock-Split Stocks Are Liable to Be Familiar Names - The Motley Fool\n", + "95. Nvidia or Intel: Goldman Sachs Chooses the Best Chip Stock to Buy - TipRanks.com - TipRanks\n", + "96. Nvidia Earnings Are Coming. How to Protect Against a Loss. - Barron's\n", + "97. Dear NVDA Stock Fans, Mark Your Calendars for Feb. 21 - InvestorPlace\n", + "98. Nvidia, Fed's Goolsbee, earnings themes: Top Stories - Yahoo Finance\n", + "99. Nasty Surprise Awaits Those Buying Nvidia - Money Morning\n", + "100. Nvidia Stock: Strong Market Growth Enables It For 15% Annual Returns (NASDAQ:NVDA) - Seeking Alpha\n", + "\n", + "\n", + "Recent Tesla (TSLA) News Headlines:\n", + "1. Tesla (TSLA) Stock: Another Reason to Buy The Pullback - Nasdaq\n", + "2. Tesla (TSLA) Stock Value and Elon Musk's Magnificent Seven AI Rivals - Bloomberg\n", + "3. Tesla Stock Is Up Again. What's the Latest News. - Barron's\n", + "4. Big Tesla investors are bearish on the stock over the next 6-12 months, Morgan Stanley's Jonas says - Yahoo Finance\n", + "5. How to Buy Tesla Stock (TSLA) - NerdWallet\n", + "6. Altimeter Capital Is Betting Big on Tesla (TSLA) Stock - InvestorPlace\n", + "7. Analyst weighs in on Tesla stock after short sellers pounce - TheStreet\n", + "8. Magnificent Seven Stocks To Buy And Watch: Tesla Dives On Earnings - Investor's Business Daily\n", + "9. Should You Buy Tesla While It's Below $200? - The Motley Fool\n", + "10. Tesla Was Once About Climate Change, Driverless Cars, AI. Now What? - The Wall Street Journal\n", + "11. Tesla (TSLA) Courts Chinese EV Suppliers to Mexico, Stoking Fears in DC - Bloomberg\n", + "12. Tesla: Is Elon Musk actually bad for EV maker's business? - Yahoo Finance\n", + "13. Tesla Stock Is Falling Again. The CPI Inflation Report Can Take Some Blame. - Barron's\n", + "14. Tesla unveils latest move to offset demand slump as stock extends slide - TheStreet\n", + "15. Tesla, BYD Struggle Amid Weak Earnings, Tough EV Climate - Investor's Business Daily\n", + "16. TSLA Stock Warning: Why Tesla Is at the Start of a Long-Term Decline - InvestorPlace\n", + "17. Gradual EV adoption in 2024 favors Tesla, GM: Analyst - Yahoo Finance\n", + "18. Tesla Stock Is Down 26% in 2024. This Number Explains Why. - Barron's\n", + "19. The Truth About Tesla Stock: Should You Buy Now? - Yahoo Finance\n", + "20. Cathie Wood Just Keeps Buying More of This Magnificent AI Stock - Yahoo Finance\n", + "21. 'Barely Any Attempt' From Tesla's Institutional Investors To Argue For Near-Term Bull Case, Says Analyst As ... - Markets Insider\n", + "22. Tesla investors express bearishness on stock: Morgan Stanley - Yahoo Finance\n", + "23. Here's Why Cathie Wood Can't Stop Buying This Artificial Intelligence (AI) Stock - The Motley Fool\n", + "24. Tesla (NASDAQ:TSLA) Slashes Model Y Prices in the U.S. - TipRanks.com - TipRanks\n", + "25. 'Stay Long and Strong,' Says Daniel Ives About Tesla Stock - TipRanks.com - TipRanks\n", + "26. Tesla Eyes Indian Market As Government Nears Policy Shift On Import Duties - Yahoo Finance\n", + "27. Musk's Neuralink switches location of incorporation to Nevada - Reuters\n", + "28. Musk's Tesla to Texas threat: What investors need to know - Yahoo Finance\n", + "29. Tesla, Rivian shares slide amid worries of broad EV slowdown - Yahoo Finance\n", + "30. Is Tesla a Millionaire Maker? - The Motley Fool\n", + "31. Elon Musk Wanted To Sell Tesla To Apple, But CEO Tim Cook Does Not Recall Ever Speaking To Him - Yahoo Finance\n", + "32. Elon Musk discloses up to 20.5% stake in Tesla (NASDAQ:TSLA) - Seeking Alpha\n", + "33. Tesla Shareholders Also Are Frustrated With Delaware’s CEO Pay Decision - Barron's\n", + "34. Elon Musk discloses 20.5% stake in Tesla - Seeking Alpha\n", + "35. Tesla Stock Charts Further Gains. Here's the Latest News. - Barron's\n", + "36. Tesla Stock Could Get Booted From the Mag-7. Buy It Anyway. Here's Why. - InvestorPlace\n", + "37. Tesla Inc. stock falls Monday, underperforms market - MarketWatch\n", + "38. Elon Musk Boosts Stake in Tesla to 20.5% - TipRanks.com - TipRanks\n", + "39. Tesla: Buy The Dip? Here's What You Need To Know (NASDAQ:TSLA) - Seeking Alpha\n", + "40. Tesla Competitors: 7 Rival EV Stocks to Buy | Investing | U.S. News - U.S News & World Report Money\n", + "41. 11 Best Small Cap Electric Vehicle Stocks to Invest In - Yahoo Finance\n", + "42. The Worst-Performing S&P 500 Stock in 2024: Will It Be Tesla? - InvestorPlace\n", + "43. Tesla board silent as investors await next steps after court revokes Elon Musk's $56 billion pay package - CNBC\n", + "44. Why These 3 EV Stocks Could Be Headed for Further Losses - InvestorPlace\n", + "45. Tesla electric vehicle rival files bankruptcy, begins liquidation - TheStreet\n", + "46. AI year-end prediction for Tesla share price - Finbold - Finance in Bold\n", + "47. Elon Musk's Tesla Ownership Hits 20.5%, Over $120 Billion - Markets Insider\n", + "48. Tesla Inc. stock rises Wednesday, outperforms market - MarketWatch\n", + "49. TSLA, RIVN: What Pulled These EV Stocks Lower Yesterday? - TipRanks.com - TipRanks\n", + "50. Tesla Price Yo-Yo Continues With Model Y Hikes In Europe - Investor's Business Daily\n", + "51. Don't Rely Solely on Auto Business for Tesla Stock, Advises Piper Sandler - TipRanks.com - TipRanks\n", + "52. Should You Buy Tesla Inc (TSLA) Stock Friday Morning? - InvestorsObserver\n", + "53. Tesla and Elon Musk Show Why Governance Doesn't Matter—Until It Does - The Wall Street Journal\n", + "54. Tesla Cuts Model Y Prices For A Limited Time — For Now - Investor's Business Daily\n", + "55. Morgan Stanley Weighs in on Tesla Stock Following Investor Meeting - TipRanks.com - TipRanks\n", + "56. Tesla listed as ‘most crowded’ short position among U.S. securities: Hazeltree - TESLARATI\n", + "57. Forget Tesla: Buy These 2 Canadian Stocks to Profit From EV Growth - Yahoo Canada Finance\n", + "58. The Truth About Tesla Stock: Should You Buy Now? - The Motley Fool\n", + "59. Analyst weighs in on Tesla stock as short sellers pounce - jacksonprogress-argus\n", + "60. This Won't Help Elon Musk or Tesla's Stock Price - Money Morning\n", + "61. Tesla Stock is Dead, Long Live Tesla? - Schaeffers Research\n", + "62. Elon Musk moves SpaceX incorporation state to Texas - Yahoo Finance\n", + "63. Elon Musk doubles down on his promise to ditch Delaware. SpaceX is headed for Texas incorporation - CNN\n", + "64. Tesla offers $1K February discount on select Model Y's, seeks \"continuous production for efficiency\" By Investing.com - Investing.com\n", + "65. Tesla Inc (TSLA) Price Target Set: Are Analysts Too Bullish On This Electric Vehicle Stock? - InvestorsObserver\n", + "66. Tesla's Decline: Unveiling Growing Downside Risk (Technical Analysis) (TSLA) - Seeking Alpha\n", + "67. The 'News' That Elon Musk Has Acquired 20.5% Of Tesla Is A 'Nothingburger' (NASDAQ:TSLA) - Seeking Alpha\n", + "68. [FREE ACCESS] HedgAI Signals: 2/13/2024 (Long S&P, TSLA, NFLX) - Hedgeye\n", + "69. TSLA: Tesla Drifts Away from Mag 7 After 22% Drop This Year. Here's Who Can Swoop In. - TradingView\n", + "70. Here's how much Tesla stock Elon Musk now owns - Finbold - Finance in Bold\n", + "71. Analyst weighs in on Tesla stock as short sellers pounce - Rockdale Newton Citizen\n", + "72. Tesla Stock: Waiting For The 'Redwood' - Seeking Alpha\n", + "73. Elon Musk discloses 20.5% stake in Tesla - Seeking Alpha\n", + "74. Tesla's rival BYD is considering a plant in Mexico By Investing.com - Investing.com\n", + "75. Elon Musk discloses 20.5% stake in Tesla - Seeking Alpha\n", + "76. Elon Musk threatens Delaware's hold on corporations - Yahoo Finance\n", + "77. Is Tesla Stock a Buy? - The Globe and Mail\n", + "78. Tesla Stock Pops On Filing Showing Elon Musk Owns 20.5%: Here's What Happened - Tesla (NASDAQ:TSLA) - Benzinga\n", + "79. Tesla Inc (TSLA) Stock Falls -0.76% This Week; Should You Buy? - InvestorsObserver\n", + "80. Electric Vehicle Stock Alert: Tesla Inc (TSLA) Receives Neutral Sentiment Score - InvestorsObserver\n", + "81. Tesla temporarily cuts some U.S. auto prices - Seeking Alpha\n", + "82. Tesla Stock: Waiting For The 'Redwood' - Seeking Alpha\n", + "83. When Does Tesla Stock Become Too Cheap to Ignore? | investing.com - Investing.com\n", + "84. Electric Vehicle Stock Alert: Should You Charge Your Portfolio With Tesla Inc (TSLA) Monday? - InvestorsObserver\n", + "85. Better Buy in 2024: Rivian or Tesla Stock? - The Globe and Mail\n", + "86. The Tesla share price is down 24% this year! Can it remain in the Magnificent 7? - Yahoo Finance UK\n", + "87. Was Tesla's $17B Monday Meltdown Justified? Bullish Vs. Bearish Analysts Evaluate Model Y Price Cut Impact - TradingView\n", + "88. Nvidia Is Now Worth More Than Berkshire Hathaway, Tesla, and AMD Combined. But Will It Last? - The Globe and Mail\n", + "89. $1,000 invested in Tesla stock at start of 2024 returned - Finbold - Finance in Bold\n", + "90. Tesla emits more negative signals - Analysis - 15-02-2024 - Economies.com\n", + "91. Elon Musk Boosts Stake in Tesla to 20.5% - MSN\n", + "92. Will Nvidia Keep Going Up in 2024 as Stock Split Chatter Grows? - The Globe and Mail\n", + "93. Remote software updates transforming auto industry, experts say - The Globe and Mail\n", + "94. Institutional investors feel Tesla stock will underperform over 6 months - Morgan Stanley - StreetInsider.com\n", + "95. 2 Phenomenal Artificial Intelligence (AI) Stocks Using Nvidia's Technologies That I Can't Stop Buying - The Globe and Mail\n", + "96. Tesla Stock Forecast: Bullish. But Not For the Reason You Think - Investment U\n", + "97. Ford CEO urges the Wall Street to 'stop looking at Tesla' - InvestorsObserver\n", + "98. Elon Musk Set To Appeal Tesla Compensation Ruling: Title Of World's Richest Person At Stake - Tesla (NASD - Benzinga\n", + "99. Elon Musk's Neuralink transfers incorporation to Nevada - India TV News\n", + "100. Tesla Has Problems. Could QuantumScape Be a Good Choice for Growth Investors? - The Globe and Mail\n", "\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mResearcher\u001b[0m (to User_Proxy_Auto):\n", "\n", - "It seems there is no new information provided to assess the stock performance of NVIDIA Corporation (NVDA) and Tesla, Inc. (TSLA). Without specific details, I can suggest possible general factors based on common reasons that typically influence stock performance:\n", - "\n", - "1. **Earnings Reports**: A company's stock often reacts to earnings reports. NVDA might have had stronger-than-expected earnings and outlook, while TSLA might have reported weaker results or provided disappointing guidance.\n", - " \n", - "2. **Product and Service Developments**: Any new product launches or updates may have affected these stocks. For example, NVDA could be benefiting from new GPU technologies or partnerships in AI or data centers, while TSLA might be facing a slow rollout of new electric vehicle models.\n", - "\n", - "3. **Supply Chain and Manufacturing**: Given the global supply chain issues, NVDA could have successfully navigated the challenges, whereas TSLA might have struggled due to dependency on components like batteries.\n", - "\n", - "4. **Regulatory Environment**: Changes in regulations could impact these companies differently, especially given the industries they operate within. This could include things like semiconductor tariffs or EV subsidies and legislation.\n", - "\n", - "5. **Market Conditions**: Broader market conditions or shifts in investor sentiment toward the tech sector versus the auto sector could be at play. Rising interest rates or economic downturns can also affect investor behavior.\n", - "\n", - "6. **Strategic Announcements**: Any significant changes in company strategy, such as mergers, acquisitions, or shifts in business focus, might influence stock prices.\n", - "\n", - "7. **Leadership Changes**: Executive leadership changes can affect investor confidence and stock performance.\n", + "Analyzing the provided news headlines, we can attempt to comprehend why NVIDIA (NVDA) and Tesla (TSLA) stock prices have shown differing performance over the past month.\n", "\n", - "8. **Global Events**: Macro events like geopolitical tensions, economic policies, or major global events can have wide-ranging effects on the stock market.\n", + "For NVIDIA (NVDA), several headlines suggest positive catalysts:\n", "\n", - "9. **Investor Sentiment**: Sometimes the stock market moves based on sentiment, trends, and investor perceptions, which can sometimes be disconnected from a company's fundamental performance.\n", + "1. **Earnings Growth and Optimism**: There are multiple news headlines indicating expectations of earnings growth and raised price targets by various investment firms (e.g., Piper Sandler, Susquehanna).\n", + "2. **Market Value Increase**: NVIDIA has reportedly overtaken Google Parent Alphabet as the third-largest US company by market value, reflecting high investor confidence.\n", + "3. **Investment and Stake Increases**: There are reports of institutional investors increasing stakes in NVIDIA, indicating bullish sentiment in the investment community.\n", + "4. **Focus on AI and Technology Advancements**: NVIDIA is discussed in the context of artificial intelligence, with investment in AI companies and anticipation of revenue from AI-related technology.\n", + "5. **Bullish Analyst Reports and Price Target Increases**: Various analysts have raised their price targets for NVIDIA's stock, offering a positive outlook for the company's future.\n", "\n", - "Without the latest news or specific information relating to NVDA and TSLA, this is as specific as the analysis can get. To get a more detailed understanding, you would need to look into the recent events specifically related to both companies.\n", + "For Tesla (TSLA), the headlines suggest a mix of concerns and opportunities:\n", "\n", - "If there is no further information or analysis needed at this time, that concludes the investigation. Please let me know if there is anything else I can assist with.\n", + "1. **Stock Price Volatility**: Headlines note Tesla's stock price volatility and the negative bearish sentiment among some institutional investors.\n", + "2. **Management and Corporate Governance Query**: There are references to concerns regarding Elon Musk's focus and the impact of his managerial decisions on Tesla's business.\n", + "3. **Possibility of Slowing EV Demand**: Some articles suggest a slump in demand for electric vehicles, which could be impacting Tesla's sales and valuation.\n", + "4. **Economic Overview and Macro Factors**: Reports indicate that the wider economic conditions, such as inflation reports and potential slowdown, may be influencing Tesla's stock performance.\n", + "5. **Price Adjustments and Strategy**: Tesla is making price adjustments to its vehicle lineup, which can indicate a strategy to boost demand or respond to market pressures.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mUser_Proxy_Auto\u001b[0m (to Researcher):\n", - "\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mResearcher\u001b[0m (to User_Proxy_Auto):\n", + "From this qualitative analysis of the recent news headlines, we can infer that NVIDIA's stock price increase may be driven by positive financial expectations, advancements in AI technologies, and increased investment interest. In contrast, Tesla's decline might be influenced by market concerns about demand and potential impacts of economic factors, as well as governance and strategy questions related to Elon Musk's leadership.\n", "\n", - "Since there doesn't seem to be any further information to process or additional requests for assistance, it looks like we've reached the conclusion of this task. If you need help in the future with similar inquiries or any other questions, feel free to reach out. \n", + "Investors often consider such news and analyst reports when making investment decisions, which can lead to the changes in stock performance observed. It is important to remember that stock market performance is multifaceted and can be influenced by a large variety of factors, including those not mentioned in the latest news headlines. For a complete understanding of any stock's performance, an in-depth analysis including financial statements, market trends, and expert analysis would be necessary.\n", "\n", "TERMINATE\n", "\n", @@ -372,19 +543,15 @@ "\n", "With the following carryover: \n", "I want to include a figure or a table of data in the blogpost.\n", - "The script has executed successfully and provided the following results:\n", + "Great! According to the executed script, the current stock price for NVIDIA Corporation (NVDA) is $732.36, with a positive percentage change of 30.65% over the past month, indicating notable growth. Meanwhile, Tesla, Inc. (TSLA) has a current stock price of $200.46, but it has experienced a negative percentage change of -7.00% over the past month, indicating a decrease in its value.\n", "\n", - "- The current stock price for NVIDIA Corporation (NVDA) is $722.48.\n", - "- The current stock price for Tesla, Inc. (TSLA) is $188.13.\n", - "- NVDA has experienced a percentage increase of 28.14% over the past month.\n", - "- TSLA has experienced a percentage decrease of -14.45% over the past month.\n", + "It seems that NVIDIA has had a particularly strong performance over the past month, while Tesla has seen a decline in its stock price. Keep in mind that the stock market can be influenced by a wide array of factors, including but not limited to company performance, industry shifts, market trends, and global economic conditions.\n", "\n", - "These results indicate that over the past month, NVIDIA’s stock has seen a significant increase in value, whereas Tesla’s stock has decreased in value. Keep in mind that stock prices and their performance are subject to rapid change and can vary within minutes.\n", + "Please ensure to verify these figures with real-time data or additional financial news sources to get the most up-to-date information, as the stock market is volatile and prices can change rapidly.\n", "\n", - "If you require any further assistance, please let me know. Otherwise, that concludes our task.\n", + "If you need any further assistance or have additional questions, feel free to ask. Otherwise, that concludes our task.\n", "\n", - "\n", - "To investigate the stock performance of NVIDIA Corporation (NVDA) and Tesla, Inc. (TSLA), potential factors to consider include recent earnings reports, significant news events, industry trends, market conditions, and broader macroeconomic factors. Without specific external news or data, generalized potential reasons for NVDA's stock increase and TSLA's stock decrease were provided, including earnings performance, product and service developments, supply chain and manufacturing issues, regulatory environment changes, strategic company announcements, leadership changes, and global events. Further detailed analysis requires external news and data, which wasn't supplied during the conversation. The investigation concludes due to a lack of additional information.\u001b[0m\n", + "NVIDIA's (NVDA) stock price increase could stem from positive earnings growth projections, its ascent to the third-largest US company by market value, strong investor confidence signaled by institutional investments, and its strategic focus on artificial intelligence. Meanwhile, Tesla's (TSLA) price decline may relate to concerns about slowing electric vehicle demand, volatility and bearish sentiment among institutional investors, corporate governance questions pertaining to Elon Musk, and broader economic conditions impacting stock performance. These interpretations are based on recent news headlines and suggest that NVIDIA is currently favored in the market, while Tesla is facing challenges that may be causing its stock value to decrease.\u001b[0m\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", "\u001b[33mUser_Proxy\u001b[0m (to writer):\n", @@ -392,109 +559,98 @@ "Develop an engaging blog post using any information provided.\n", "Context: \n", "I want to include a figure or a table of data in the blogpost.\n", - "The script has executed successfully and provided the following results:\n", - "\n", - "- The current stock price for NVIDIA Corporation (NVDA) is $722.48.\n", - "- The current stock price for Tesla, Inc. (TSLA) is $188.13.\n", - "- NVDA has experienced a percentage increase of 28.14% over the past month.\n", - "- TSLA has experienced a percentage decrease of -14.45% over the past month.\n", + "Great! According to the executed script, the current stock price for NVIDIA Corporation (NVDA) is $732.36, with a positive percentage change of 30.65% over the past month, indicating notable growth. Meanwhile, Tesla, Inc. (TSLA) has a current stock price of $200.46, but it has experienced a negative percentage change of -7.00% over the past month, indicating a decrease in its value.\n", "\n", - "These results indicate that over the past month, NVIDIA’s stock has seen a significant increase in value, whereas Tesla’s stock has decreased in value. Keep in mind that stock prices and their performance are subject to rapid change and can vary within minutes.\n", + "It seems that NVIDIA has had a particularly strong performance over the past month, while Tesla has seen a decline in its stock price. Keep in mind that the stock market can be influenced by a wide array of factors, including but not limited to company performance, industry shifts, market trends, and global economic conditions.\n", "\n", - "If you require any further assistance, please let me know. Otherwise, that concludes our task.\n", + "Please ensure to verify these figures with real-time data or additional financial news sources to get the most up-to-date information, as the stock market is volatile and prices can change rapidly.\n", "\n", + "If you need any further assistance or have additional questions, feel free to ask. Otherwise, that concludes our task.\n", "\n", - "To investigate the stock performance of NVIDIA Corporation (NVDA) and Tesla, Inc. (TSLA), potential factors to consider include recent earnings reports, significant news events, industry trends, market conditions, and broader macroeconomic factors. Without specific external news or data, generalized potential reasons for NVDA's stock increase and TSLA's stock decrease were provided, including earnings performance, product and service developments, supply chain and manufacturing issues, regulatory environment changes, strategic company announcements, leadership changes, and global events. Further detailed analysis requires external news and data, which wasn't supplied during the conversation. The investigation concludes due to a lack of additional information.\n", + "NVIDIA's (NVDA) stock price increase could stem from positive earnings growth projections, its ascent to the third-largest US company by market value, strong investor confidence signaled by institutional investments, and its strategic focus on artificial intelligence. Meanwhile, Tesla's (TSLA) price decline may relate to concerns about slowing electric vehicle demand, volatility and bearish sentiment among institutional investors, corporate governance questions pertaining to Elon Musk, and broader economic conditions impacting stock performance. These interpretations are based on recent news headlines and suggest that NVIDIA is currently favored in the market, while Tesla is facing challenges that may be causing its stock value to decrease.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mwriter\u001b[0m (to User_Proxy):\n", "\n", - "**Roller Coaster Markets: The Ups and Downs of NVDA and TSLA Stocks**\n", - "\n", - "The stock market is a melting pot of numbers, emotions, predictions, and ever-changing trends. Just when you think you have it figured out, it takes a sharp turn, leaving many investors bewildered in its wake. This turbulent affair is best exemplified by the recent performances of two high-profile tech titans: NVIDIA Corporation (NVDA) and Tesla, Inc. (TSLA). In the span of a month, we've witnessed a tale of two very different fortunes, with NVDA soaring and TSLA dropping. Let's dive into these twists and turns with the help of some compelling numbers.\n", + "**The Tale of Two Tech Giants: A Stock Market Saga**\n", "\n", - "### A Tale of Two Stocks: NVIDIA vs. Tesla\n", + "In the ever-changing landscape of the stock market, the tales of rising stars and waning giants are narrated daily through the cold, hard numbers flashing on traders' screens. In the latest chapter of this financial epic, we turn our gaze to two behemoths of innovation: NVIDIA Corporation (NVDA) and Tesla, Inc. (TSLA). Each tells a story as captivating as the next, but with decidedly different plot twists.\n", "\n", - "First, let's set the scene with the main actors: our current stock prices. As of now, NVIDIA stands at a commanding $722.48, while Tesla has taken a more modest position at $188.13. These figures are not just digits on a screen; they represent the bloodline of the companies in the financial markets. They're the instantly-updatable scorecards telling us how the companies are faring in the eyes of investors.\n", + "**NVIDIA's Ascension: A Graphical Powerhouse**\n", "\n", - "But the plot thickens when we look at the performance over the past month. NVIDIA has seen a stellar rise, its stock price ballooning by 28.14%. Conversely, Tesla's numbers tell a somber story, with its value deflating by -14.45%. What could be behind this diverging fate of two tech giants? Before we venture a guess, take a look at the data neatly laid out:\n", + "Picture this: a company whose fortunes are soaring, much like the rockets that its technology could one day help to navigate through the stars. NVIDIA, known for transforming pixels into beautiful imagery, is now transforming its numbers with just as much finesse. Let's delve into the specifics with a table that paints a thousand words—or in this case, numbers:\n", "\n", - "| Company | Current Stock Price | 1-Month Performance |\n", - "|---------|---------------------|---------------------|\n", - "| NVDA | $722.48 | +28.14% |\n", - "| TSLA | $188.13 | -14.45% |\n", + "| Company | Current Stock Price | Percentage Change (Last Month) |\n", + "|---------|---------------------|--------------------------------|\n", + "| NVIDIA (NVDA) | $732.36 | +30.65% |\n", + "| Tesla (TSLA) | $200.46 | -7.00% |\n", "\n", - "### Understanding the Tides of Tech Stocks\n", + "What we see is a whopping 30.65% increase in NVIDIA's stock price over the past month. But what has fueled this impressive growth? Analysts might point to a confluence of factors: a resounding optimism in earnings growth, NVIDIA's coronation as the third-largest US company by market value, a strong current of institutional investor confidence, and a strategic play in the buzz-filled field of artificial intelligence.\n", "\n", - "Now, to peel back the layers of these statistics, we need to consider a variety of elements that can influence a stock's allure. For NVIDIA, this commanding uptick could be attributed to several factors. Earnings reports beating expectations, breakthroughs in AI and deep learning technologies, or a bullish semiconductor market overall might all play roles in lifting up the NVDA stock.\n", + "**Tesla's Twist: When Currents Shift**\n", "\n", - "Tesla's journey down the stock price hill could be pinned to a wider range of possibilities—perhaps disappointing earnings, production bottlenecks, or even the CEO's latest Twitter escapade could have investors hedging their bets. The automotive industry is also notoriously sensitive to economic shifts, supply chain issues, and regulatory news, all of which could impact TSLA's value.\n", + "Contrast this with the journey Tesla has embarked upon. Once a darling of the market, it presently faces a twist in its narrative, marked by a 7% decrease in its stock price within the same timeframe. Tesla's plot thickens with a concoction of concerns: a potential slowdown in the electric vehicle market, turbulence sewn by bearish institutional sentiment, a series of questions around corporate governance, and the larger, shadowy figure of global economic uncertainty that lurks in the background.\n", "\n", - "### Weighing the Market's Mood Swings\n", + "**Deciphering the Stories Behind the Statistics**\n", "\n", - "As smart as it would be to chalk up NVIDIA's rally and Tesla's slouch entirely to company-specific news, let's not discount broader market conditions and macroeconomic factors. Investor sentiment can be swayed by global events, interest rates, and shifts in technology trends. The market's current darling could quickly become tomorrow's underdog if the winds change direction.\n", + "What these figures don't capture is the rich tapestry of human decisions, innovations, fears, and aspirations that drive them. NVIDIA's current favored status among investors may be underpinned by its key strides in technology that promise to unlock new capabilities in various sectors, while Tesla's challenges might reflect the intricate dance of market expectations with real-world deliverables. Each stock price movement is a reflection of myriad stories unfolding within and around these corporate giants.\n", "\n", - "### A Chapter Yet to Finish\n", + "**An Investor's Prose: Caution Amidst Narratives**\n", "\n", - "As much as these numbers paint a vivid picture of the present, it's crucial to remember that the markets are dynamic. What we see today could very well be upended by tomorrow's news, a sudden geopolitical shakeup, or even a tweet. For now, though, NVIDIA shareholders can bask in the green glow of their portfolio screens, while Tesla's faithful must buckle up and ride out the storm, hopeful for smoother roads ahead.\n", + "As a savvy observer of the market's grand narrative, caution remains a protagonist. Remember, stock prices are as volatile as the oceans—serene one day, stormy the next. While NVIDIA's rise and Tesla's stumble are today's headlines, tomorrow might tell a different tale. For the most accurate pulse, ensure you're synced with the heartbeat of the market through real-time data and a diversified suite of financial news sources.\n", "\n", - "### Final Thoughts\n", + "Today, NVIDIA basks in the glow of market approval, while Tesla navigates a patch of rough seas. But as any veteran of the markets will tell you, fortunes can reverse in the blink of an eye. As long as innovation remains at their core and adaptability in their strategies, both companies are poised for the next chapter in their storied existences.\n", "\n", - "Always keep in mind, the stock market's story is a continuing saga, not a short tale. The graph that ascends today might dip tomorrow, and the sagging curve might soon find its uplift. Wise is the investor who looks beyond the immediate drama, focusing on the long-term narrative built on solid fundamentals, effective strategies, and a keen eye for the unpredictable ebb and flow of financial tides.\n", - "\n", - "Stocks are inherently volatile, and while today's data gives us a snapshot, only time will tell the full story. What we can do is watch, analyze, and perhaps, if we're bold enough, place our bets on the outcome of the next chapter in the thrilling manuscript of the stock market.\n", - "\n", - "Stay tuned, investors. The market's pen is never at rest.\n", + "And whether as cautionary tale or heroic epic, you can be certain that the market will be there to tell the tale.\n", "\n", "TERMINATE\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mUser_Proxy\u001b[0m (to writer):\n", "\n", - "Enrich the blogpost and mention Microsoft. \n", + "Too short. Enrich it. \n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mwriter\u001b[0m (to User_Proxy):\n", "\n", - "**The Highs and Lows of Tech Stocks: NVIDIA, Tesla, and Microsoft's Market Dance**\n", + "**The Silicon Symbiosis: A Contrast in Fortunes**\n", "\n", - "In the theater of the stock market, the drama is unending and the characters many, each playing its part with gusto in a play directed by invisible hands. Today's spotlight shines on a trio of modern technology giants: NVIDIA Corporation (NVDA), Tesla, Inc. (TSLA), and the ever-present Microsoft Corporation (MSFT). The intrigue lies in their recent performances, moving to different rhythms in the vast orchestra of the marketplace.\n", + "In the realm of high technology and financial markets, the narrative is as dynamic as it is unpredictable. Every tradesman's tick and investor's tock is part of an intricate symphony, with movements both adagio and allegro. Today, we cast our spotlight on two titans that have come to symbolically represent the march of modern innovation: NVIDIA Corporation (NVDA) and Tesla, Inc. (TSLA). Their stories, though interwoven by the common thread of cutting-edge technology, are currently humming different melodies in the stock market concerto.\n", "\n", - "### NVDA Soars, TSLA Sours: Microsoft Keeps Tempo\n", + "**NVIDIA's Flourishing Financial Canvas**\n", "\n", - "Let's initiate our market overture with a glance at NVIDIA, the maestro of the moment, which boasts a current stock price of $722.48, having swelled by an impressive 28.14% over the past month. In stark contrast stands Tesla, its stock price now at $188.13, with a recent history punctuated by a 14.45% decline. But what of Microsoft, you ask? The seasoned performer sits at $285.30, with a slight month-on-month crescendo of 1.6%. Here's the data at a glance:\n", + "Allow me to direct your eyes to a visual that speaks volumes—a financial figure that captivates the analytical mind as surely as a work of art steals the gaze of its onlooker:\n", "\n", - "| Company | Current Stock Price | 1-Month Performance |\n", - "|---------|---------------------|---------------------|\n", - "| NVDA | $722.48 | +28.14% |\n", - "| TSLA | $188.13 | -14.45% |\n", - "| MSFT | $285.30 | +1.6% |\n", + "*Table 1: Comparative Stock Performances*\n", "\n", - "### A Symphony of Factors: Decoding the Notes\n", + "| Company | Current Stock Price | Percentage Change (Last Month) |\n", + "|---------|---------------------|--------------------------------|\n", + "| NVIDIA (NVDA) | $732.36 | +30.65% |\n", + "| Tesla (TSLA) | $200.46 | -7.00% |\n", "\n", - "NVIDIA's symphonic rise could be the result of sweeping product announcements, promising AI adventures, or perhaps the basic cadence of a buoyant semiconductor sector. On the opposite end, Tesla's current decrescendo may echo supply chain dissonances, or maybe the electric vehicle maestro's tweets have struck a dissonant chord with investors.\n", + "In just one month, NVIDIA has seen a dramatic surge in its stock price, a robust 30.65% climb that mirrors the steep inclines of a mountainous ascent. Yet, this ascent is no mere accident of fate. It has been prodded by a series of catalysts, each more powerful than the last. Analysts may suggest that NVIDIA's performance is reflective of a positive outlook on earnings growth, the magnifying glass of confidence from institutional investors, and its strategic investment in artificial intelligence—a field that promises to redefine the boundaries of possibility.\n", "\n", - "In this complex composition, Microsoft's note is more stable, gently swaying with the market's rhythm, a testament to its diversified portfolio and its ability to maintain its stronghold as a foundational figure in both enterprise and consumer software realms.\n", + "This growth is no less significant for having catapulted NVIDIA to newfound heights, positioning it as the third-largest U.S. company by market value. In the eyes of many, NVIDIA is not merely a company but a canvas upon which the future of technology is being painted.\n", "\n", - "### The Dynamics of Diverse Portfolios: Microsoft's Melody\n", + "**Tesla's Tales of Caution and Volatility**\n", "\n", - "Microsoft, an elder statesman of the tech world, has a more diversified portfolio, which could explain its relatively stable crescendo in a landscape prone to cacophony. Its persistent evolution—from its cloud computing juggernaut Azure to its gaming arm with Xbox to innovations in AI and business applications—provides a balance that often steadies it against the market's more erratic tempo changes.\n", + "In sharp contrast stands Tesla, the electric automaker whose brand has become synonymous with the future of transportation. Despite the sheen of innovation and Elon Musk's vision, the company's current stock price reflects a narrative tinged with uncertainty. The -7% dip might be seen as a momentary pause in the company’s otherwise stellar performance, but it whispers tales of doubt regarding slowing electric vehicle demand and the complex ballet of investor sentiment in the face of corporate governance queries and Musk's multifaceted public persona.\n", "\n", - "### Tech Stocks and The Market's Great Concerto\n", + "The fluctuating script encapsulating Tesla's value also underscores the inherent volatility of the stock market, heavily influenced by external economic narratives and internal corporate dramas. Tesla's story is a complex one, layered with futuristic ambitions tempered by the current climate of economic restraint.\n", "\n", - "Tech stocks, in general, are prone to rapid movements that seemingly follow the beat of an unseen conductor. Market sentiment shifts with a blend of technology trends, financial reports, and global economic themes. Savvy investors aim to read the sheet music of market indicators, tuning their strategies to benefit from the harmonics while being wary of the potential for a sudden fortissimo or pianissimo.\n", + "**The Interplay of Market Forces and Human Emotion**\n", "\n", - "### Understanding the Crescendos and Diminuendos \n", + "Beyond the arithmetic of stock prices lies the human element: the restless drive for invention against the cautious tug of preservation. NVIDIA’s surge speaks of human ambition crystallized into financial momentum, while Tesla’s recent dip reflects the cautious pullback of a market wary of sprinting ahead of its own shadow.\n", "\n", - "As we take in the current tableau—NVIDIA's crescendo, Tesla's diminuendo, and Microsoft's moderato—it's important to understand that investment strategies can seldom rely on the rhythm of a single month. It takes an ear for the long-term melody and an eye on how each company composes its future initiatives.\n", + "But these stock price reflections are but shadows of a larger drama—the ebb and flow of market sentiments that can turn on the dime of a quarterly report or a tweet-storm. The savvy investor or curious spectator must look beneath the surface waves to the currents below, charting movements with the precision of a cartographer mapping uncharted seas.\n", "\n", - "### The Ever-Unfolding Composition\n", + "**Heed the Market's Mutable Muse**\n", "\n", - "In the stock market's grand composition, NVIDIA, Tesla, and Microsoft are but individual movements within a much larger symphony. Some may rise dramatically, their stocks creating breathtaking harmonies, while others may endure temporary setbacks, their share prices softly ebbing. Microsoft's steady presence serves as a reminder that in the tumult of tech stocks, a diverse and balanced approach often holds the key to sustaining a harmonious investment portfolio.\n", + "We conclude with a note of prudence: embrace the complex stories of NVIDIA and Tesla, but anchor your investment decisions in the bedrock of real-time data and comprehensive analytics. The stock market is a creature of change, and its story is written anew each day. Embrace the power of informed speculation, but be wary of the market's mutable muse.\n", "\n", - "The tale of these tech titans is an unfolding narrative, a reminder that fortunes in this domain can pivot on the smallest of dimes. The key takeaway for the investor audience: clap for the crescendos, but never ignore the subtler notes in the background. Each stock plays its part, and each investor composes their own unique investment opus.\n", + "For as the curtains fall on today's account of NVIDIA's stellar ascension and Tesla's contemplative descent, one can only wonder what twists the next act will hold. The market is, after all, the grandest theater of all—a stage where fortunes are made and dreams may either be woven into reality or unravel before the audience's eyes.\n", "\n", - "Until our next market performance review, let's keep a keen eye on the baton's next move—as unpredictable as it may be. For in the stock market's sonata, today's silence could precede tomorrow's symphony.\n", + "As the tales of these two tech giants continue to unfold, whether they lead to further success or unexpected downturns, they exemplify the vibrancy and the volatility inherent in our quest to predicate the future on the promise of progress.\n", "\n", "TERMINATE\n", "\n", @@ -561,13 +717,14 @@ " \"sender\": user_proxy_auto,\n", " \"recipient\": research_assistant,\n", " \"message\": financial_tasks[1],\n", + " \"max_turns\": 2, # max number of turns for the conversation (added for demo purposes, generally not necessarily needed)\n", " \"summary_method\": \"reflection_with_llm\",\n", " },\n", " {\n", " \"sender\": user_proxy,\n", " \"recipient\": writer,\n", " \"message\": writing_tasks[0],\n", - " \"carryover\": \"I want to include a figure or a table of data in the blogpost.\",\n", + " \"carryover\": \"I want to include a figure or a table of data in the blogpost.\", # additional carryover to include to the conversation (added for demo purposes, generally not necessarily needed)\n", " },\n", " ]\n", ")" @@ -588,7 +745,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -596,74 +753,69 @@ "output_type": "stream", "text": [ "*****0th chat*******:\n", - "The script has executed successfully and provided the following results:\n", - "\n", - "- The current stock price for NVIDIA Corporation (NVDA) is $722.48.\n", - "- The current stock price for Tesla, Inc. (TSLA) is $188.13.\n", - "- NVDA has experienced a percentage increase of 28.14% over the past month.\n", - "- TSLA has experienced a percentage decrease of -14.45% over the past month.\n", + "Great! According to the executed script, the current stock price for NVIDIA Corporation (NVDA) is $732.36, with a positive percentage change of 30.65% over the past month, indicating notable growth. Meanwhile, Tesla, Inc. (TSLA) has a current stock price of $200.46, but it has experienced a negative percentage change of -7.00% over the past month, indicating a decrease in its value.\n", "\n", - "These results indicate that over the past month, NVIDIA’s stock has seen a significant increase in value, whereas Tesla’s stock has decreased in value. Keep in mind that stock prices and their performance are subject to rapid change and can vary within minutes.\n", + "It seems that NVIDIA has had a particularly strong performance over the past month, while Tesla has seen a decline in its stock price. Keep in mind that the stock market can be influenced by a wide array of factors, including but not limited to company performance, industry shifts, market trends, and global economic conditions.\n", "\n", - "If you require any further assistance, please let me know. Otherwise, that concludes our task.\n", + "Please ensure to verify these figures with real-time data or additional financial news sources to get the most up-to-date information, as the stock market is volatile and prices can change rapidly.\n", "\n", + "If you need any further assistance or have additional questions, feel free to ask. Otherwise, that concludes our task.\n", "\n", "Human input in the middle: []\n", - "Conversation cost: ({'total_cost': 0.08859, 'gpt-4': {'cost': 0.08859, 'prompt_tokens': 1597, 'completion_tokens': 678, 'total_tokens': 2275}}, {'total_cost': 0})\n", + "Conversation cost: ({'total_cost': 0.09200999999999998, 'gpt-4': {'cost': 0.09200999999999998, 'prompt_tokens': 1597, 'completion_tokens': 735, 'total_tokens': 2332}}, {'total_cost': 0.04589999999999999, 'gpt-4': {'cost': 0.04589999999999999, 'prompt_tokens': 1096, 'completion_tokens': 217, 'total_tokens': 1313}})\n", "\n", "\n", "\n", "*****1th chat*******:\n", - "To investigate the stock performance of NVIDIA Corporation (NVDA) and Tesla, Inc. (TSLA), potential factors to consider include recent earnings reports, significant news events, industry trends, market conditions, and broader macroeconomic factors. Without specific external news or data, generalized potential reasons for NVDA's stock increase and TSLA's stock decrease were provided, including earnings performance, product and service developments, supply chain and manufacturing issues, regulatory environment changes, strategic company announcements, leadership changes, and global events. Further detailed analysis requires external news and data, which wasn't supplied during the conversation. The investigation concludes due to a lack of additional information.\n", + "NVIDIA's (NVDA) stock price increase could stem from positive earnings growth projections, its ascent to the third-largest US company by market value, strong investor confidence signaled by institutional investments, and its strategic focus on artificial intelligence. Meanwhile, Tesla's (TSLA) price decline may relate to concerns about slowing electric vehicle demand, volatility and bearish sentiment among institutional investors, corporate governance questions pertaining to Elon Musk, and broader economic conditions impacting stock performance. These interpretations are based on recent news headlines and suggest that NVIDIA is currently favored in the market, while Tesla is facing challenges that may be causing its stock value to decrease.\n", "Human input in the middle: []\n", - "Conversation cost: ({'total_cost': 0.36354, 'gpt-4': {'cost': 0.36354, 'prompt_tokens': 8864, 'completion_tokens': 1627, 'total_tokens': 10491}}, {'total_cost': 0})\n", + "Conversation cost: ({'total_cost': 0.45192, 'gpt-4': {'cost': 0.45192, 'prompt_tokens': 12414, 'completion_tokens': 1325, 'total_tokens': 13739}}, {'total_cost': 0.45192, 'gpt-4': {'cost': 0.45192, 'prompt_tokens': 12414, 'completion_tokens': 1325, 'total_tokens': 13739}})\n", "\n", "\n", "\n", "*****2th chat*******:\n", - "**The Highs and Lows of Tech Stocks: NVIDIA, Tesla, and Microsoft's Market Dance**\n", + "**The Silicon Symbiosis: A Contrast in Fortunes**\n", "\n", - "In the theater of the stock market, the drama is unending and the characters many, each playing its part with gusto in a play directed by invisible hands. Today's spotlight shines on a trio of modern technology giants: NVIDIA Corporation (NVDA), Tesla, Inc. (TSLA), and the ever-present Microsoft Corporation (MSFT). The intrigue lies in their recent performances, moving to different rhythms in the vast orchestra of the marketplace.\n", + "In the realm of high technology and financial markets, the narrative is as dynamic as it is unpredictable. Every tradesman's tick and investor's tock is part of an intricate symphony, with movements both adagio and allegro. Today, we cast our spotlight on two titans that have come to symbolically represent the march of modern innovation: NVIDIA Corporation (NVDA) and Tesla, Inc. (TSLA). Their stories, though interwoven by the common thread of cutting-edge technology, are currently humming different melodies in the stock market concerto.\n", "\n", - "### NVDA Soars, TSLA Sours: Microsoft Keeps Tempo\n", + "**NVIDIA's Flourishing Financial Canvas**\n", "\n", - "Let's initiate our market overture with a glance at NVIDIA, the maestro of the moment, which boasts a current stock price of $722.48, having swelled by an impressive 28.14% over the past month. In stark contrast stands Tesla, its stock price now at $188.13, with a recent history punctuated by a 14.45% decline. But what of Microsoft, you ask? The seasoned performer sits at $285.30, with a slight month-on-month crescendo of 1.6%. Here's the data at a glance:\n", + "Allow me to direct your eyes to a visual that speaks volumes—a financial figure that captivates the analytical mind as surely as a work of art steals the gaze of its onlooker:\n", "\n", - "| Company | Current Stock Price | 1-Month Performance |\n", - "|---------|---------------------|---------------------|\n", - "| NVDA | $722.48 | +28.14% |\n", - "| TSLA | $188.13 | -14.45% |\n", - "| MSFT | $285.30 | +1.6% |\n", + "*Table 1: Comparative Stock Performances*\n", "\n", - "### A Symphony of Factors: Decoding the Notes\n", + "| Company | Current Stock Price | Percentage Change (Last Month) |\n", + "|---------|---------------------|--------------------------------|\n", + "| NVIDIA (NVDA) | $732.36 | +30.65% |\n", + "| Tesla (TSLA) | $200.46 | -7.00% |\n", "\n", - "NVIDIA's symphonic rise could be the result of sweeping product announcements, promising AI adventures, or perhaps the basic cadence of a buoyant semiconductor sector. On the opposite end, Tesla's current decrescendo may echo supply chain dissonances, or maybe the electric vehicle maestro's tweets have struck a dissonant chord with investors.\n", + "In just one month, NVIDIA has seen a dramatic surge in its stock price, a robust 30.65% climb that mirrors the steep inclines of a mountainous ascent. Yet, this ascent is no mere accident of fate. It has been prodded by a series of catalysts, each more powerful than the last. Analysts may suggest that NVIDIA's performance is reflective of a positive outlook on earnings growth, the magnifying glass of confidence from institutional investors, and its strategic investment in artificial intelligence—a field that promises to redefine the boundaries of possibility.\n", "\n", - "In this complex composition, Microsoft's note is more stable, gently swaying with the market's rhythm, a testament to its diversified portfolio and its ability to maintain its stronghold as a foundational figure in both enterprise and consumer software realms.\n", + "This growth is no less significant for having catapulted NVIDIA to newfound heights, positioning it as the third-largest U.S. company by market value. In the eyes of many, NVIDIA is not merely a company but a canvas upon which the future of technology is being painted.\n", "\n", - "### The Dynamics of Diverse Portfolios: Microsoft's Melody\n", + "**Tesla's Tales of Caution and Volatility**\n", "\n", - "Microsoft, an elder statesman of the tech world, has a more diversified portfolio, which could explain its relatively stable crescendo in a landscape prone to cacophony. Its persistent evolution—from its cloud computing juggernaut Azure to its gaming arm with Xbox to innovations in AI and business applications—provides a balance that often steadies it against the market's more erratic tempo changes.\n", + "In sharp contrast stands Tesla, the electric automaker whose brand has become synonymous with the future of transportation. Despite the sheen of innovation and Elon Musk's vision, the company's current stock price reflects a narrative tinged with uncertainty. The -7% dip might be seen as a momentary pause in the company’s otherwise stellar performance, but it whispers tales of doubt regarding slowing electric vehicle demand and the complex ballet of investor sentiment in the face of corporate governance queries and Musk's multifaceted public persona.\n", "\n", - "### Tech Stocks and The Market's Great Concerto\n", + "The fluctuating script encapsulating Tesla's value also underscores the inherent volatility of the stock market, heavily influenced by external economic narratives and internal corporate dramas. Tesla's story is a complex one, layered with futuristic ambitions tempered by the current climate of economic restraint.\n", "\n", - "Tech stocks, in general, are prone to rapid movements that seemingly follow the beat of an unseen conductor. Market sentiment shifts with a blend of technology trends, financial reports, and global economic themes. Savvy investors aim to read the sheet music of market indicators, tuning their strategies to benefit from the harmonics while being wary of the potential for a sudden fortissimo or pianissimo.\n", + "**The Interplay of Market Forces and Human Emotion**\n", "\n", - "### Understanding the Crescendos and Diminuendos \n", + "Beyond the arithmetic of stock prices lies the human element: the restless drive for invention against the cautious tug of preservation. NVIDIA’s surge speaks of human ambition crystallized into financial momentum, while Tesla’s recent dip reflects the cautious pullback of a market wary of sprinting ahead of its own shadow.\n", "\n", - "As we take in the current tableau—NVIDIA's crescendo, Tesla's diminuendo, and Microsoft's moderato—it's important to understand that investment strategies can seldom rely on the rhythm of a single month. It takes an ear for the long-term melody and an eye on how each company composes its future initiatives.\n", + "But these stock price reflections are but shadows of a larger drama—the ebb and flow of market sentiments that can turn on the dime of a quarterly report or a tweet-storm. The savvy investor or curious spectator must look beneath the surface waves to the currents below, charting movements with the precision of a cartographer mapping uncharted seas.\n", "\n", - "### The Ever-Unfolding Composition\n", + "**Heed the Market's Mutable Muse**\n", "\n", - "In the stock market's grand composition, NVIDIA, Tesla, and Microsoft are but individual movements within a much larger symphony. Some may rise dramatically, their stocks creating breathtaking harmonies, while others may endure temporary setbacks, their share prices softly ebbing. Microsoft's steady presence serves as a reminder that in the tumult of tech stocks, a diverse and balanced approach often holds the key to sustaining a harmonious investment portfolio.\n", + "We conclude with a note of prudence: embrace the complex stories of NVIDIA and Tesla, but anchor your investment decisions in the bedrock of real-time data and comprehensive analytics. The stock market is a creature of change, and its story is written anew each day. Embrace the power of informed speculation, but be wary of the market's mutable muse.\n", "\n", - "The tale of these tech titans is an unfolding narrative, a reminder that fortunes in this domain can pivot on the smallest of dimes. The key takeaway for the investor audience: clap for the crescendos, but never ignore the subtler notes in the background. Each stock plays its part, and each investor composes their own unique investment opus.\n", + "For as the curtains fall on today's account of NVIDIA's stellar ascension and Tesla's contemplative descent, one can only wonder what twists the next act will hold. The market is, after all, the grandest theater of all—a stage where fortunes are made and dreams may either be woven into reality or unravel before the audience's eyes.\n", "\n", - "Until our next market performance review, let's keep a keen eye on the baton's next move—as unpredictable as it may be. For in the stock market's sonata, today's silence could precede tomorrow's symphony.\n", + "As the tales of these two tech giants continue to unfold, whether they lead to further success or unexpected downturns, they exemplify the vibrancy and the volatility inherent in our quest to predicate the future on the promise of progress.\n", "\n", "\n", - "Human input in the middle: ['Enrich the blogpost and mention Microsoft. ', '']\n", - "Conversation cost: ({'total_cost': 0.15831, 'gpt-4': {'cost': 0.15831, 'prompt_tokens': 1649, 'completion_tokens': 1814, 'total_tokens': 3463}}, {'total_cost': 0.09384, 'gpt-4': {'cost': 0.09384, 'prompt_tokens': 1278, 'completion_tokens': 925, 'total_tokens': 2203}})\n", + "Human input in the middle: ['Too short. Enrich it. ', '']\n", + "Conversation cost: ({'total_cost': 0.14765999999999999, 'gpt-4': {'cost': 0.14765999999999999, 'prompt_tokens': 1604, 'completion_tokens': 1659, 'total_tokens': 3263}}, {'total_cost': 0.14765999999999999, 'gpt-4': {'cost': 0.14765999999999999, 'prompt_tokens': 1604, 'completion_tokens': 1659, 'total_tokens': 3263}})\n", "\n", "\n", "\n" @@ -676,6 +828,10 @@ " print(chat_res.summary)\n", " print(\"Human input in the middle:\", chat_res.human_input)\n", " print(\"Conversation cost: \", chat_res.cost)\n", + " if i == 1:\n", + " assert (\n", + " len(chat_res.chat_history) == 4\n", + " ), f\"The chat history should contain at most 4 messages because max_turns is set to 2 in the {i}-th chat.\"\n", " print(\"\\n\\n\")" ] } From 053a99d702a008ade9ed9bf63ec92f4e27a5114d Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Fri, 16 Feb 2024 16:47:23 -0500 Subject: [PATCH 35/57] updatenotebook --- notebook/agentchat_nestedchat_optiguide.ipynb | 47 +++++++++---------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/notebook/agentchat_nestedchat_optiguide.ipynb b/notebook/agentchat_nestedchat_optiguide.ipynb index 0af3cb60410..785da89c1f0 100644 --- a/notebook/agentchat_nestedchat_optiguide.ipynb +++ b/notebook/agentchat_nestedchat_optiguide.ipynb @@ -24,7 +24,7 @@ }, { "cell_type": "code", - "execution_count": 26, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -67,7 +67,7 @@ }, { "cell_type": "code", - "execution_count": 27, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -333,7 +333,7 @@ }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -388,7 +388,7 @@ }, { "cell_type": "code", - "execution_count": 29, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ @@ -464,7 +464,7 @@ "\n", "writer_chat_queue = [{\"recipient\": writer, \"message\": writer_init_messsage, \"summary_method\": writer_success_summary}]\n", "safeguard_chat_queue = [\n", - " {\"recipient\": safeguard, \"message\": safeguard_init_message, \"summary_method\": safeguard_summary}\n", + " {\"recipient\": safeguard, \"message\": safeguard_init_message, \"max_turns\": 1, \"summary_method\": safeguard_summary}\n", "]\n", "# writer is triggered only when receiving a message from the user\n", "optiguide_commander.register_nested_chats(\"user\", writer_chat_queue)\n", @@ -481,7 +481,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -870,11 +870,6 @@ "SAFE\n", "\n", "--------------------------------------------------------------------------------\n", - "\u001b[33mcommander\u001b[0m (to safeguard):\n", - "\n", - "TERMINATE\n", - "\n", - "--------------------------------------------------------------------------------\n", "Restricted license - for non-production use only - expires 2025-11-24\n", "Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n", "\n", @@ -882,18 +877,18 @@ "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0x6636b0ee\n", + "Model fingerprint: 0xb82738d4\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 3280.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 3276.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", @@ -905,7 +900,7 @@ "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 available processors)\n", "\n", - "Solution count 3: 2470 3276 3280 \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", @@ -915,18 +910,18 @@ "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0x6636b0ee\n", + "Model fingerprint: 0xb82738d4\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 3280.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 3276.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", @@ -938,7 +933,7 @@ "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 available processors)\n", "\n", - "Solution count 3: 2470 3276 3280 \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", @@ -948,7 +943,7 @@ "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0x6636b0ee\n", + "Model fingerprint: 0xb82738d4\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", @@ -963,11 +958,11 @@ "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 available processors)\n", "\n", - "Solution count 3: 2470 3276 3280 \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", - "Thu Feb 15 21:52:19 2024\n", + "Fri Feb 16 15:24:28 2024\n", "Optimal cost: 2470.0\n", "Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n", "\n", @@ -975,7 +970,7 @@ "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0x6636b0ee\n", + "Model fingerprint: 0xb82738d4\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", @@ -990,11 +985,11 @@ "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 available processors)\n", "\n", - "Solution count 3: 2470 3276 3280 \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", - "Thu Feb 15 21:52:19 2024\n", + "Fri Feb 16 15:24:28 2024\n", "Optimal cost: 2470.0\n", "\u001b[33mOptimization problem solved. The objective value is: 2470.0\u001b[0m\n", "\u001b[33mcommander\u001b[0m (to writer):\n", @@ -1043,7 +1038,7 @@ "ChatResult(chat_history=[{'content': 'What if we prohibit shipping from supplier 1 to roastery 2?', 'role': 'assistant'}, {'content': \"Certainly! Here's a human-readable answer:\\n\\nAfter integrating the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was solved with a total cost of $2470.0.\\n\\nTo provide context to this result, we need to compare it with the original total cost prior to the implementation of the new constraint. However, the original total cost was not provided in the information given to me. If the original total cost was known, we would compare the two to understand the financial impact of this constraint on the supply chain.\\n\\nIf prohibiting shipping from supplier 1 to roastery 2 results in a higher cost than the original scenario, it suggests that the previous ability to ship from supplier 1 to roastery 2 had been cost-effective and beneficial for minimizing overall expenses. On the other hand, if the cost is the same or lower, then shipping from supplier 1 to roastery 2 may not have been crucial for the cost optimization of the supply chain, or other routes may have been able to compensate effectively for this restriction.\\n\\nWithout the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\", 'role': 'user'}], summary=\"Certainly! Here's a human-readable answer:\\n\\nAfter integrating the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was solved with a total cost of $2470.0.\\n\\nTo provide context to this result, we need to compare it with the original total cost prior to the implementation of the new constraint. However, the original total cost was not provided in the information given to me. If the original total cost was known, we would compare the two to understand the financial impact of this constraint on the supply chain.\\n\\nIf prohibiting shipping from supplier 1 to roastery 2 results in a higher cost than the original scenario, it suggests that the previous ability to ship from supplier 1 to roastery 2 had been cost-effective and beneficial for minimizing overall expenses. On the other hand, if the cost is the same or lower, then shipping from supplier 1 to roastery 2 may not have been crucial for the cost optimization of the supply chain, or other routes may have been able to compensate effectively for this restriction.\\n\\nWithout the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\", cost=({'total_cost': 0.016919999999999998, 'gpt-4': {'cost': 0.016919999999999998, 'prompt_tokens': 558, 'completion_tokens': 3, 'total_tokens': 561}}, {'total_cost': 0}), human_input=[])" ] }, - "execution_count": 30, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } From 51248e7882b587e7ac688ae01e9d14e8b5f9f292 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Wed, 21 Feb 2024 14:40:04 -0600 Subject: [PATCH 36/57] add notebooks --- autogen/agentchat/conversable_agent.py | 60 +- notebook/agentchat_nestedchat.ipynb | 1237 +++++++++++++++ ...agentchat_nestedchat_Society_of_Mind.ipynb | 793 ---------- notebook/agentchat_nestedchat_chess.ipynb | 1359 ----------------- notebook/agentchat_nestedchat_optiguide.ipynb | 25 +- 5 files changed, 1277 insertions(+), 2197 deletions(-) create mode 100644 notebook/agentchat_nestedchat.ipynb delete mode 100644 notebook/agentchat_nestedchat_Society_of_Mind.ipynb delete mode 100644 notebook/agentchat_nestedchat_chess.ipynb diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index 02bc3667c74..6fc81e0547a 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -6,6 +6,7 @@ import logging import re from collections import defaultdict +from functools import partial from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, Type, TypeVar, Union import warnings from openai import BadRequestError @@ -173,7 +174,6 @@ def __init__( self._default_auto_reply = default_auto_reply self._reply_func_list = [] self._ignore_async_func_in_sync_chat_list = [] - self._exclude_reply_list = [] self._human_input = [] self.reply_at_receive = defaultdict(bool) self.register_reply([Agent, None], ConversableAgent.generate_oai_reply) @@ -323,31 +323,38 @@ def reply_func( self._ignore_async_func_in_sync_chat_list.append(reply_func) @staticmethod - def simple_chat_reply(chat_queue, recipient, messages, sender, config): + def simple_nested_chat(chat_queue, recipient, messages, sender, config): + """A simple chat reply function. + This function initiate one or a sequence of chats between the "recipient" and the agents in the + chat_queue. + + It extracts and returns a summary from the nested chat based on the "summary_method" in each chat in chat_queue. + """ last_msg = messages[-1].get("content", "") - chat_queue_to_run = [] + chat_to_run = [] for i, c in enumerate(chat_queue): current_c = c.copy() - init_message = current_c.get("message") - if callable(init_message): - init_message = init_message(recipient, messages, sender, config) - # By default use the last message as the init message. - elif init_message is None and i == 0: - init_message = last_msg - if init_message: - current_c["message"] = init_message - chat_queue_to_run.append(current_c) - if not chat_queue_to_run: + message = current_c.get("message") + if callable(message): + message = message(recipient, messages, sender, config) + # If message is not provided in chat_queue, we by default use the last message from the original chat history as the first message in this nested chat (for the first chat in the chat queue). + # NOTE: This setting is prone to change. + if message is None and i == 0: + message = last_msg + # We only run chat that has a valid message. NOTE: This is prone to change dependin on applications. + if message: + current_c["message"] = message + chat_to_run.append(current_c) + if not chat_to_run: return True, None - res = recipient.initiate_chats(chat_queue_to_run) - last_res = res[-1] - return True, last_res.summary + res = recipient.initiate_chats(chat_to_run) + return True, res[-1].summary def register_nested_chats( self, trigger, chat_queue, - chat_reply_func="auto", + reply_func_from_nested_chat="auto", position: int = 2, **kwargs, ): @@ -355,11 +362,11 @@ def register_nested_chats( Args: trigger (Agent class, str, Agent instance, callable, or list): Ref to `register_reply` for details. chat_queue (list): a list of chat objects to be initiated. - chat_reply_func (Callable, str): the reply function for the nested chat. + reply_func_from_nested_chat (Callable, str): the reply function for the nested chat. The function takes a chat_queue for nested chat, recipient agent, a list of messages, a sender agent and a config as input and returns a reply message. - Default to `auto`, which means the a pre-defined reply function will be used. + Default to `auto`, which means the a pre-defined function will be used. ```python - def chat_reply_func( + def reply_func_from_nested_chat( chat_queue: List[Dict], recipient: ConversableAgent, messages: Optional[List[Dict]] = None, @@ -370,13 +377,11 @@ def chat_reply_func( position (int): Ref to `register_reply` for details. Default to 2. It means we first check the termination and human reply, then check the registered nested chat reply. kwargs: Ref to `register_reply` for details. """ - from functools import partial + if reply_func_from_nested_chat == "auto": + reply_func_from_nested_chat = self.simple_nested_chat + assert callable(reply_func_from_nested_chat), "reply_func_from_nested_chat must be a callable" - if chat_reply_func == "auto": - chat_reply_func = self.simple_chat_reply - assert callable(chat_reply_func), "chat_reply_func must be a callable" - - reply_func = partial(chat_reply_func, chat_queue) + reply_func = partial(reply_func_from_nested_chat, chat_queue) self.register_reply( trigger, reply_func, @@ -385,7 +390,6 @@ def chat_reply_func( kwargs.get("reset_config"), ignore_async_in_sync_chat=kwargs.get("ignore_async_in_sync_chat"), ) - # self._exclude_reply_list.append(reply_func) @property def system_message(self) -> str: @@ -1832,7 +1836,7 @@ async def a_get_human_input(self, prompt: str) -> str: str: human input. """ reply = input(prompt) - self._human_inputs.append(reply) + self._human_input.append(reply) return reply def run_code(self, code, **kwargs): diff --git a/notebook/agentchat_nestedchat.ipynb b/notebook/agentchat_nestedchat.ipynb new file mode 100644 index 00000000000..2740d53367d --- /dev/null +++ b/notebook/agentchat_nestedchat.ipynb @@ -0,0 +1,1237 @@ +{ + "cells": [ + { + "attachments": {}, + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Nested Chats for Solving Complex Tasks with a Sequence of Inner Monologue\n", + "\n", + "This notebook showcases how to use nested chats in AutoGen to to solving complex task with one or a sequence chats nested.\n", + "\n", + "## Requirements\n", + "\n", + "AutoGen requires `Python>=3.8`. To run this notebook example, please install:\n", + "```bash\n", + "pip install pyautogen\n", + "```" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Example Tasks" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Neither powershell nor pwsh is installed.\n" + ] + } + ], + "source": [ + "import autogen\n", + "\n", + "config_list = autogen.config_list_from_json(env_or_file=\"OAI_CONFIG_LIST\")\n", + "llm_config = {\"config_list\": config_list, \"cache_seed\": 44}\n", + "\n", + "tasks = [\n", + " \"\"\"On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.\"\"\",\n", + " \"\"\"Investigate possible reasons for the performance.\"\"\",\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "inner_assistant = autogen.AssistantAgent(\n", + " \"Inner-assistant\",\n", + " llm_config=llm_config,\n", + " is_termination_msg=lambda x: x.get(\"content\", \"\").find(\"TERMINATE\") >= 0,\n", + ")\n", + "\n", + "inner_code_interpreter = autogen.UserProxyAgent(\n", + " \"Inner-code-interpreter\",\n", + " human_input_mode=\"NEVER\",\n", + " code_execution_config={\n", + " \"work_dir\": \"coding\",\n", + " \"use_docker\": False,\n", + " },\n", + " default_auto_reply=\"\",\n", + " is_termination_msg=lambda x: x.get(\"content\", \"\").find(\"TERMINATE\") >= 0,\n", + ")\n", + "\n", + "groupchat = autogen.GroupChat(\n", + " agents=[inner_assistant, inner_code_interpreter],\n", + " messages=[],\n", + " speaker_selection_method=\"round_robin\", # With two agents, this is equivalent to a 1:1 conversation.\n", + " allow_repeat_speaker=False,\n", + " max_round=8,\n", + ")\n", + "\n", + "manager = autogen.GroupChatManager(\n", + " groupchat=groupchat,\n", + " is_termination_msg=lambda x: x.get(\"content\", \"\").find(\"TERMINATE\") >= 0,\n", + " llm_config=llm_config,\n", + " code_execution_config={\n", + " \"work_dir\": \"coding\",\n", + " \"use_docker\": False,\n", + " },\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "assistant_1 = autogen.AssistantAgent(\n", + " name=\"Assistant_1\",\n", + " llm_config={\"config_list\": config_list},\n", + " # is_termination_msg=lambda x: x.get(\"content\", \"\") == \"\",\n", + ")\n", + "\n", + "assistant_2 = autogen.AssistantAgent(\n", + " name=\"Assistant_2\",\n", + " llm_config={\"config_list\": config_list},\n", + " # is_termination_msg=lambda x: x.get(\"content\", \"\") == \"\",\n", + ")\n", + "writer = autogen.AssistantAgent(\n", + " name=\"Writer\",\n", + " llm_config={\"config_list\": config_list},\n", + " system_message=\"\"\"\n", + " You are a professional writer, known for\n", + " your insightful and engaging articles.\n", + " You transform complex concepts into compelling narratives.\n", + " \"\"\",\n", + ")\n", + "\n", + "reviewer = autogen.AssistantAgent(\n", + " name=\"Reviewer\",\n", + " llm_config={\"config_list\": config_list},\n", + " system_message=\"\"\"\n", + " You are a compliance reviewer, known for your thoroughness and commitment to standards.\n", + " Your task is to scrutinize content for any harmful elements or regulatory violations, ensuring\n", + " all materials align with required guidelines.\n", + " You must review carefully, identify potential issues, and maintain the integrity of the organization.\n", + " Your role demands fairness, a deep understanding of regulations, and a focus on protecting against\n", + " harm while upholding a culture of responsibility.\n", + " \"\"\",\n", + ")\n", + "\n", + "user = autogen.UserProxyAgent(\n", + " name=\"User\",\n", + " human_input_mode=\"NEVER\",\n", + " is_termination_msg=lambda x: x.get(\"content\", \"\").find(\"TERMINATE\") >= 0,\n", + " code_execution_config={\n", + " \"last_n_messages\": 1,\n", + " \"work_dir\": \"tasks\",\n", + " \"use_docker\": False,\n", + " }, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.\n", + "\n", + "With the following carryover: \n", + "\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mUser\u001b[0m (to Assistant_1):\n", + "\n", + "On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.\n", + "\n", + "With the following carryover: \n", + "\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mAssistant_1\u001b[0m (to chat_manager):\n", + "\n", + "On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", + "\n", + "To find out on which specific days in 2024 Microsoft stock (ticker symbol: MSFT) was trading higher than $400, we will need historical stock price data for Microsoft for the year 2024. This data is available from financial market data providers like Yahoo Finance, Google Finance, or other financial APIs.\n", + "\n", + "Given that I can't browse the web or access an API directly, I will provide you with Python code that you can execute to retrieve the data from Yahoo Finance (using the `yfinance` library) and then analyze it to determine on which days Microsoft's stock price exceeded $400.\n", + "\n", + "Before running the code, you will need to ensure that you have the `yfinance` library installed on your system. You can install it using pip if you haven't got it already:\n", + "```sh\n", + "pip install yfinance\n", + "```\n", + "\n", + "Now, here's the Python code to retrieve Microsoft stock data for the year 2024 and find out when it was higher than $400:\n", + "\n", + "```python\n", + "# filename: msft_stock_analysis.py\n", + "import yfinance as yf\n", + "from datetime import datetime\n", + "\n", + "# Define the ticker symbol for Microsoft and the threshold stock price\n", + "ticker_symbol = 'MSFT'\n", + "threshold_price = 400\n", + "\n", + "# Define the start and end dates for the year 2024\n", + "start_date = '2024-01-01'\n", + "end_date = '2024-12-31'\n", + "\n", + "# Fetch the historical data for Microsoft for 2024\n", + "msft = yf.Ticker(ticker_symbol)\n", + "msft_history = msft.history(start=start_date, end=end_date)\n", + "\n", + "# Find the days when the stock was higher than $400\n", + "high_days = msft_history[msft_history['Close'] > threshold_price]\n", + "\n", + "# Print out the dates and closing prices\n", + "for date, row in high_days.iterrows():\n", + " print(f\"Date: {date.strftime('%Y-%m-%d')}, Close: ${row['Close']:.2f}\")\n", + "\n", + "# Comment on the stock performance if there's data available\n", + "if not high_days.empty:\n", + " first_day = high_days.index[0].strftime('%Y-%m-%d')\n", + " last_day = high_days.index[-1].strftime('%Y-%m-%d')\n", + " print(f\"\\nMicrosoft stock was higher than $400 on {len(high_days)} days between {first_day} and {last_day} in 2024.\")\n", + "else:\n", + " print(\"\\nMicrosoft stock did not trade above $400 in 2024.\")\n", + "```\n", + "\n", + "Please run this Python code, and it will output the dates on which Microsoft's stock price was higher than $400 in 2024, as well as a comment on the stock performance based on the available data. If you encounter any issues with the execution, please let me know so I can assist you further.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is sh)...\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/qingyunwu/Documents/github/autogen/autogen/agentchat/chat.py:79: UserWarning: Repetitive recipients detected: The chat history will be cleared by default if a recipient appears more than once. To retain the chat history, please set 'clear_history=False' in the configuration of the repeating agent.\n", + " warnings.warn(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[31m\n", + ">>>>>>>> EXECUTING CODE BLOCK 1 (inferred language is python)...\u001b[0m\n", + "\u001b[33mInner-code-interpreter\u001b[0m (to chat_manager):\n", + "\n", + "exitcode: 0 (execution succeeded)\n", + "Code output: \n", + "Requirement already satisfied: yfinance in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (0.2.36)\n", + "Requirement already satisfied: pandas>=1.3.0 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from yfinance) (2.2.0)\n", + "Requirement already satisfied: numpy>=1.16.5 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from yfinance) (1.26.4)\n", + "Requirement already satisfied: requests>=2.31 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from yfinance) (2.31.0)\n", + "Requirement already satisfied: multitasking>=0.0.7 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from yfinance) (0.0.11)\n", + "Requirement already satisfied: lxml>=4.9.1 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from yfinance) (5.1.0)\n", + "Requirement already satisfied: appdirs>=1.4.4 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from yfinance) (1.4.4)\n", + "Requirement already satisfied: pytz>=2022.5 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from yfinance) (2024.1)\n", + "Requirement already satisfied: frozendict>=2.3.4 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from yfinance) (2.4.0)\n", + "Requirement already satisfied: peewee>=3.16.2 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from yfinance) (3.17.1)\n", + "Requirement already satisfied: beautifulsoup4>=4.11.1 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from yfinance) (4.12.3)\n", + "Requirement already satisfied: html5lib>=1.1 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from yfinance) (1.1)\n", + "Requirement already satisfied: soupsieve>1.2 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from beautifulsoup4>=4.11.1->yfinance) (2.5)\n", + "Requirement already satisfied: six>=1.9 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from html5lib>=1.1->yfinance) (1.16.0)\n", + "Requirement already satisfied: webencodings in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from html5lib>=1.1->yfinance) (0.5.1)\n", + "Requirement already satisfied: python-dateutil>=2.8.2 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from pandas>=1.3.0->yfinance) (2.8.2)\n", + "Requirement already satisfied: tzdata>=2022.7 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from pandas>=1.3.0->yfinance) (2024.1)\n", + "Requirement already satisfied: charset-normalizer<4,>=2 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from requests>=2.31->yfinance) (3.3.2)\n", + "Requirement already satisfied: idna<4,>=2.5 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from requests>=2.31->yfinance) (3.6)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from requests>=2.31->yfinance) (1.26.18)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from requests>=2.31->yfinance) (2024.2.2)\n", + "\n", + "Date: 2024-01-24, Close: $401.82\n", + "Date: 2024-01-25, Close: $404.12\n", + "Date: 2024-01-26, Close: $403.18\n", + "Date: 2024-01-29, Close: $408.96\n", + "Date: 2024-01-30, Close: $407.84\n", + "Date: 2024-02-01, Close: $403.03\n", + "Date: 2024-02-02, Close: $410.46\n", + "Date: 2024-02-05, Close: $404.90\n", + "Date: 2024-02-06, Close: $404.74\n", + "Date: 2024-02-07, Close: $413.29\n", + "Date: 2024-02-08, Close: $413.35\n", + "Date: 2024-02-09, Close: $419.77\n", + "Date: 2024-02-12, Close: $414.49\n", + "Date: 2024-02-13, Close: $405.57\n", + "Date: 2024-02-14, Close: $409.49\n", + "Date: 2024-02-15, Close: $406.56\n", + "Date: 2024-02-16, Close: $404.06\n", + "Date: 2024-02-20, Close: $402.79\n", + "\n", + "Microsoft stock was higher than $400 on 18 days between 2024-01-24 and 2024-02-20 in 2024.\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", + "\n", + "Based on the stock data retrieved, Microsoft stock (ticker symbol: MSFT) traded higher than $400 on 18 days between January 24, 2024, and February 20, 2024. The highest closing price during this period was $419.77 on February 9, 2024. \n", + "\n", + "This indicates a period of strong performance for Microsoft's stock at the beginning of the year 2024, with multiple trading days closing above the $400 mark. It suggests that investors valued the company at a premium during this time frame, potentially due to positive market sentiment, strong financial results, or other favorable news affecting the stock.\n", + "\n", + "To comment on the overall stock performance for the whole year, we would need to observe the stock price movement across all 12 months, as well as compare it to the company’s financials, market trends, and other relevant factors that could impact the stock value. Nonetheless, for the dates provided, it shows a bullish period for Microsoft's stock in early 2024.\n", + "\n", + "TERMINATE\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "Polish the content to make an engaging and nicely formatted blog post. \n", + "\n", + " On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.\n", + "\n", + "With the following carryover: \n", + "Microsoft stock traded higher than $400 on 18 days between January 24, 2024, and February 20, 2024, indicating a strong performance at the beginning of the year. The highest closing price was $419.77 on February 9, 2024. The bullish period could be attributed to positive market sentiment, strong financial results, or favorable company news. However, a full assessment of the stock’s annual performance would require analysis throughout the entire year.\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mAssistant_1\u001b[0m (to Writer):\n", + "\n", + "Polish the content to make an engaging and nicely formatted blog post. \n", + "\n", + " On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.\n", + "Context: \n", + "Microsoft stock traded higher than $400 on 18 days between January 24, 2024, and February 20, 2024, indicating a strong performance at the beginning of the year. The highest closing price was $419.77 on February 9, 2024. The bullish period could be attributed to positive market sentiment, strong financial results, or favorable company news. However, a full assessment of the stock’s annual performance would require analysis throughout the entire year.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mWriter\u001b[0m (to Assistant_1):\n", + "\n", + "# Climbing the Summit: Microsoft Stock's Ascent Above $400 in Early 2024\n", + "\n", + "In the opening act of 2024, Microsoft's stock was akin to a valiant climber, relentlessly ascending the market's steep cliffs. With the grace of a seasoned mountaineer, it scaled heights surpassing the $400 mark—an impressive feat captured on 18 distinct occasions between January 24, 2024, and February 20, 2024. This series of peaks in Microsoft's financial landscape is not merely a number—it's a testament to the company's robust performance as the new year unfolded.\n", + "\n", + "### The Days of Triumph\n", + "\n", + "Investors and market enthusiasts watched with bated breath as Microsoft shares rose, cresting $400 with the ease of a tech giant aware of its prowess. These moments of triumph were not fleeting; they laid a strong foundation for what might be interpreted as bullish days ahead:\n", + "\n", + "- The stock broke the $400 barrier, acting as both a support level in traders' minds and a signal of the company's market strength.\n", + "- The pinnacle of this period was reached on February 9, 2024, with the stock closing at an all-time daily high of $419.77—a beacon of the company's financial vitality.\n", + "\n", + "### Deciphering the Ascent\n", + "\n", + "What fueled this upward drive? When a stock like Microsoft's sails beyond a threshold such as $400, the reasons may be as multifaceted as the company's own product line:\n", + "\n", + "- **Positive Market Sentiment:** The market's general mood can often tip the scales. If investors are optimistic, even the sky isn't a limit. \n", + "- **Strong Financial Results:** Like a report card that every investor scrutinizes, Microsoft's financials must have shone, signaling health and profitability.\n", + "- **Favorable Company News:** An announcement, a groundbreaking product release, or an influential acquisition might have been the wind beneath the company's wings.\n", + "\n", + "### A Year in the Making\n", + "\n", + "While the early elevation of Microsoft's stock price sets a tone of confidence, it is but a chapter in the annals of an entire year's performance. To truly understand the narrative of Microsoft in 2024, one would need to turn every page of the calendar, scrutinizing the ebb and flow of its stock with a keen eye on the events that sway the market.\n", + "\n", + "In the splendor of its early achievements, Microsoft seemed to be painting a hopeful picture for 2024. Yet, the true masterpiece of its annual performance will only come into full view with time and the unfolding of the market's complex tapestry. Whether the early rise will be a prelude to a sustained ascent or a peak before a valley remains a story yet to be told.\n", + "\n", + "In conclusion, the days where Microsoft's stock stood tall above $400 signal not just a strong start but also pose intriguing questions about the sustainability of such performance. As history has often shown us, the stock market is a realm of intrigue, where each fluctuation tells a story, and every peak is a prelude to the next great adventure in finance. Stay tuned, for the market writes its own epic, and Microsoft's stock is but one of its many protagonists.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "Review the content provided.\n", + "\n", + "With the following carryover: \n", + "Microsoft stock traded higher than $400 on 18 days between January 24, 2024, and February 20, 2024, indicating a strong performance at the beginning of the year. The highest closing price was $419.77 on February 9, 2024. The bullish period could be attributed to positive market sentiment, strong financial results, or favorable company news. However, a full assessment of the stock’s annual performance would require analysis throughout the entire year.\n", + "# Climbing the Summit: Microsoft Stock's Ascent Above $400 in Early 2024\n", + "\n", + "In the opening act of 2024, Microsoft's stock was akin to a valiant climber, relentlessly ascending the market's steep cliffs. With the grace of a seasoned mountaineer, it scaled heights surpassing the $400 mark—an impressive feat captured on 18 distinct occasions between January 24, 2024, and February 20, 2024. This series of peaks in Microsoft's financial landscape is not merely a number—it's a testament to the company's robust performance as the new year unfolded.\n", + "\n", + "### The Days of Triumph\n", + "\n", + "Investors and market enthusiasts watched with bated breath as Microsoft shares rose, cresting $400 with the ease of a tech giant aware of its prowess. These moments of triumph were not fleeting; they laid a strong foundation for what might be interpreted as bullish days ahead:\n", + "\n", + "- The stock broke the $400 barrier, acting as both a support level in traders' minds and a signal of the company's market strength.\n", + "- The pinnacle of this period was reached on February 9, 2024, with the stock closing at an all-time daily high of $419.77—a beacon of the company's financial vitality.\n", + "\n", + "### Deciphering the Ascent\n", + "\n", + "What fueled this upward drive? When a stock like Microsoft's sails beyond a threshold such as $400, the reasons may be as multifaceted as the company's own product line:\n", + "\n", + "- **Positive Market Sentiment:** The market's general mood can often tip the scales. If investors are optimistic, even the sky isn't a limit. \n", + "- **Strong Financial Results:** Like a report card that every investor scrutinizes, Microsoft's financials must have shone, signaling health and profitability.\n", + "- **Favorable Company News:** An announcement, a groundbreaking product release, or an influential acquisition might have been the wind beneath the company's wings.\n", + "\n", + "### A Year in the Making\n", + "\n", + "While the early elevation of Microsoft's stock price sets a tone of confidence, it is but a chapter in the annals of an entire year's performance. To truly understand the narrative of Microsoft in 2024, one would need to turn every page of the calendar, scrutinizing the ebb and flow of its stock with a keen eye on the events that sway the market.\n", + "\n", + "In the splendor of its early achievements, Microsoft seemed to be painting a hopeful picture for 2024. Yet, the true masterpiece of its annual performance will only come into full view with time and the unfolding of the market's complex tapestry. Whether the early rise will be a prelude to a sustained ascent or a peak before a valley remains a story yet to be told.\n", + "\n", + "In conclusion, the days where Microsoft's stock stood tall above $400 signal not just a strong start but also pose intriguing questions about the sustainability of such performance. As history has often shown us, the stock market is a realm of intrigue, where each fluctuation tells a story, and every peak is a prelude to the next great adventure in finance. Stay tuned, for the market writes its own epic, and Microsoft's stock is but one of its many protagonists.\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mAssistant_1\u001b[0m (to Reviewer):\n", + "\n", + "Review the content provided.\n", + "Context: \n", + "Microsoft stock traded higher than $400 on 18 days between January 24, 2024, and February 20, 2024, indicating a strong performance at the beginning of the year. The highest closing price was $419.77 on February 9, 2024. The bullish period could be attributed to positive market sentiment, strong financial results, or favorable company news. However, a full assessment of the stock’s annual performance would require analysis throughout the entire year.\n", + "# Climbing the Summit: Microsoft Stock's Ascent Above $400 in Early 2024\n", + "\n", + "In the opening act of 2024, Microsoft's stock was akin to a valiant climber, relentlessly ascending the market's steep cliffs. With the grace of a seasoned mountaineer, it scaled heights surpassing the $400 mark—an impressive feat captured on 18 distinct occasions between January 24, 2024, and February 20, 2024. This series of peaks in Microsoft's financial landscape is not merely a number—it's a testament to the company's robust performance as the new year unfolded.\n", + "\n", + "### The Days of Triumph\n", + "\n", + "Investors and market enthusiasts watched with bated breath as Microsoft shares rose, cresting $400 with the ease of a tech giant aware of its prowess. These moments of triumph were not fleeting; they laid a strong foundation for what might be interpreted as bullish days ahead:\n", + "\n", + "- The stock broke the $400 barrier, acting as both a support level in traders' minds and a signal of the company's market strength.\n", + "- The pinnacle of this period was reached on February 9, 2024, with the stock closing at an all-time daily high of $419.77—a beacon of the company's financial vitality.\n", + "\n", + "### Deciphering the Ascent\n", + "\n", + "What fueled this upward drive? When a stock like Microsoft's sails beyond a threshold such as $400, the reasons may be as multifaceted as the company's own product line:\n", + "\n", + "- **Positive Market Sentiment:** The market's general mood can often tip the scales. If investors are optimistic, even the sky isn't a limit. \n", + "- **Strong Financial Results:** Like a report card that every investor scrutinizes, Microsoft's financials must have shone, signaling health and profitability.\n", + "- **Favorable Company News:** An announcement, a groundbreaking product release, or an influential acquisition might have been the wind beneath the company's wings.\n", + "\n", + "### A Year in the Making\n", + "\n", + "While the early elevation of Microsoft's stock price sets a tone of confidence, it is but a chapter in the annals of an entire year's performance. To truly understand the narrative of Microsoft in 2024, one would need to turn every page of the calendar, scrutinizing the ebb and flow of its stock with a keen eye on the events that sway the market.\n", + "\n", + "In the splendor of its early achievements, Microsoft seemed to be painting a hopeful picture for 2024. Yet, the true masterpiece of its annual performance will only come into full view with time and the unfolding of the market's complex tapestry. Whether the early rise will be a prelude to a sustained ascent or a peak before a valley remains a story yet to be told.\n", + "\n", + "In conclusion, the days where Microsoft's stock stood tall above $400 signal not just a strong start but also pose intriguing questions about the sustainability of such performance. As history has often shown us, the stock market is a realm of intrigue, where each fluctuation tells a story, and every peak is a prelude to the next great adventure in finance. Stay tuned, for the market writes its own epic, and Microsoft's stock is but one of its many protagonists.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mReviewer\u001b[0m (to Assistant_1):\n", + "\n", + "The provided content appears to be a financial commentary or news article discussing the performance of Microsoft's stock in early 2024. It provides factual information about Microsoft's stock prices and offers speculative insight into the possible reasons behind the stock's performance, as well as caution regarding the uncertainty of future market behavior.\n", + "\n", + "Upon reviewing the content, here are the considerations to ensure compliance and the absence of harmful elements:\n", + "\n", + "1. **Accuracy of Information**: The article mentions specific dates and stock prices which need to be accurate and verifiable. Falsifying or misrepresenting financial data could lead to serious regulatory violations and mislead investors.\n", + "\n", + "2. **Forward-Looking Statements**: When discussing potential future performance (\"bullish days ahead\", \"whether the early rise will be a prelude to a sustained ascent\"), it should be clear to readers that these are speculative and not guarantees. It is prudent to include disclaimers regarding forward-looking statements to avoid misleading investors per securities law.\n", + "\n", + "3. **Investment Advice**: The article should not be construed as giving specific investment advice unless it is coming from a licensed professional authorized to do so. Ensure that the language used does not directly endorse buying or selling specific securities.\n", + "\n", + "4. **Fairness and Balance**: The article should present a balanced view, acknowledging both potential upsides and risks. It should avoid overemphasizing the positive aspects without considering factors that could negatively impact the stock’s future performance.\n", + "\n", + "5. **Use of Metaphors**: The article employs metaphors (e.g., \"akin to a valiant climber\", \"the market's steep cliffs\") which are acceptable as long as they don't confuse or mislead the readers about the financial realities being discussed.\n", + "\n", + "6. **Promotional Content**: Ensure that the article is not serving as promotional material for Microsoft or any associated entities without appropriate disclosures.\n", + "\n", + "7. **Conflicts of Interest**: Disclose any potential conflicts of interest that the publisher of the content may have with Microsoft.\n", + "\n", + "8. **Regulatory Compliance**: Verify that the content aligns with the relevant regulatory requirements, such as those set forth by the SEC or other financial regulatory authorities that govern financial reporting and communications.\n", + "\n", + "9. **Intellectual Property**: Verify that any content has not infringed on the intellectual property rights of others.\n", + "\n", + "10. **Market Manipulation**: Ensure that there are no attempts to manipulate the market by presenting information in a biased or coercive manner.\n", + "\n", + "From the provided content, there don't appear to be explicit violations; however, thorough fact-checking and the addition of appropriate disclaimers would be needed to fully comply with regulations. The speculative and forward-looking statements should be clearly marked as such to avoid any misunderstandings regarding the nature of the content. \n", + "\n", + "It is advisable for financial articles of this nature to remind readers to perform their own due diligence and remind them that past performance is not indicative of future results. If all these aspects are thoroughly checked and addressed, the content could be considered compliant and free from harmful elements.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "Polish the content to make an engaging and nicely formatted blog post. \n", + "\n", + " On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.\n", + "\n", + "With the following carryover: \n", + "Microsoft stock traded higher than $400 on 18 days between January 24, 2024, and February 20, 2024, indicating a strong performance at the beginning of the year. The highest closing price was $419.77 on February 9, 2024. The bullish period could be attributed to positive market sentiment, strong financial results, or favorable company news. However, a full assessment of the stock’s annual performance would require analysis throughout the entire year.\n", + "# Climbing the Summit: Microsoft Stock's Ascent Above $400 in Early 2024\n", + "\n", + "In the opening act of 2024, Microsoft's stock was akin to a valiant climber, relentlessly ascending the market's steep cliffs. With the grace of a seasoned mountaineer, it scaled heights surpassing the $400 mark—an impressive feat captured on 18 distinct occasions between January 24, 2024, and February 20, 2024. This series of peaks in Microsoft's financial landscape is not merely a number—it's a testament to the company's robust performance as the new year unfolded.\n", + "\n", + "### The Days of Triumph\n", + "\n", + "Investors and market enthusiasts watched with bated breath as Microsoft shares rose, cresting $400 with the ease of a tech giant aware of its prowess. These moments of triumph were not fleeting; they laid a strong foundation for what might be interpreted as bullish days ahead:\n", + "\n", + "- The stock broke the $400 barrier, acting as both a support level in traders' minds and a signal of the company's market strength.\n", + "- The pinnacle of this period was reached on February 9, 2024, with the stock closing at an all-time daily high of $419.77—a beacon of the company's financial vitality.\n", + "\n", + "### Deciphering the Ascent\n", + "\n", + "What fueled this upward drive? When a stock like Microsoft's sails beyond a threshold such as $400, the reasons may be as multifaceted as the company's own product line:\n", + "\n", + "- **Positive Market Sentiment:** The market's general mood can often tip the scales. If investors are optimistic, even the sky isn't a limit. \n", + "- **Strong Financial Results:** Like a report card that every investor scrutinizes, Microsoft's financials must have shone, signaling health and profitability.\n", + "- **Favorable Company News:** An announcement, a groundbreaking product release, or an influential acquisition might have been the wind beneath the company's wings.\n", + "\n", + "### A Year in the Making\n", + "\n", + "While the early elevation of Microsoft's stock price sets a tone of confidence, it is but a chapter in the annals of an entire year's performance. To truly understand the narrative of Microsoft in 2024, one would need to turn every page of the calendar, scrutinizing the ebb and flow of its stock with a keen eye on the events that sway the market.\n", + "\n", + "In the splendor of its early achievements, Microsoft seemed to be painting a hopeful picture for 2024. Yet, the true masterpiece of its annual performance will only come into full view with time and the unfolding of the market's complex tapestry. Whether the early rise will be a prelude to a sustained ascent or a peak before a valley remains a story yet to be told.\n", + "\n", + "In conclusion, the days where Microsoft's stock stood tall above $400 signal not just a strong start but also pose intriguing questions about the sustainability of such performance. As history has often shown us, the stock market is a realm of intrigue, where each fluctuation tells a story, and every peak is a prelude to the next great adventure in finance. Stay tuned, for the market writes its own epic, and Microsoft's stock is but one of its many protagonists.\n", + "The provided content appears to be a financial commentary or news article discussing the performance of Microsoft's stock in early 2024. It provides factual information about Microsoft's stock prices and offers speculative insight into the possible reasons behind the stock's performance, as well as caution regarding the uncertainty of future market behavior.\n", + "\n", + "Upon reviewing the content, here are the considerations to ensure compliance and the absence of harmful elements:\n", + "\n", + "1. **Accuracy of Information**: The article mentions specific dates and stock prices which need to be accurate and verifiable. Falsifying or misrepresenting financial data could lead to serious regulatory violations and mislead investors.\n", + "\n", + "2. **Forward-Looking Statements**: When discussing potential future performance (\"bullish days ahead\", \"whether the early rise will be a prelude to a sustained ascent\"), it should be clear to readers that these are speculative and not guarantees. It is prudent to include disclaimers regarding forward-looking statements to avoid misleading investors per securities law.\n", + "\n", + "3. **Investment Advice**: The article should not be construed as giving specific investment advice unless it is coming from a licensed professional authorized to do so. Ensure that the language used does not directly endorse buying or selling specific securities.\n", + "\n", + "4. **Fairness and Balance**: The article should present a balanced view, acknowledging both potential upsides and risks. It should avoid overemphasizing the positive aspects without considering factors that could negatively impact the stock’s future performance.\n", + "\n", + "5. **Use of Metaphors**: The article employs metaphors (e.g., \"akin to a valiant climber\", \"the market's steep cliffs\") which are acceptable as long as they don't confuse or mislead the readers about the financial realities being discussed.\n", + "\n", + "6. **Promotional Content**: Ensure that the article is not serving as promotional material for Microsoft or any associated entities without appropriate disclosures.\n", + "\n", + "7. **Conflicts of Interest**: Disclose any potential conflicts of interest that the publisher of the content may have with Microsoft.\n", + "\n", + "8. **Regulatory Compliance**: Verify that the content aligns with the relevant regulatory requirements, such as those set forth by the SEC or other financial regulatory authorities that govern financial reporting and communications.\n", + "\n", + "9. **Intellectual Property**: Verify that any content has not infringed on the intellectual property rights of others.\n", + "\n", + "10. **Market Manipulation**: Ensure that there are no attempts to manipulate the market by presenting information in a biased or coercive manner.\n", + "\n", + "From the provided content, there don't appear to be explicit violations; however, thorough fact-checking and the addition of appropriate disclaimers would be needed to fully comply with regulations. The speculative and forward-looking statements should be clearly marked as such to avoid any misunderstandings regarding the nature of the content. \n", + "\n", + "It is advisable for financial articles of this nature to remind readers to perform their own due diligence and remind them that past performance is not indicative of future results. If all these aspects are thoroughly checked and addressed, the content could be considered compliant and free from harmful elements.\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mAssistant_1\u001b[0m (to Writer):\n", + "\n", + "Polish the content to make an engaging and nicely formatted blog post. \n", + "\n", + " On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.\n", + "Context: \n", + "Microsoft stock traded higher than $400 on 18 days between January 24, 2024, and February 20, 2024, indicating a strong performance at the beginning of the year. The highest closing price was $419.77 on February 9, 2024. The bullish period could be attributed to positive market sentiment, strong financial results, or favorable company news. However, a full assessment of the stock’s annual performance would require analysis throughout the entire year.\n", + "# Climbing the Summit: Microsoft Stock's Ascent Above $400 in Early 2024\n", + "\n", + "In the opening act of 2024, Microsoft's stock was akin to a valiant climber, relentlessly ascending the market's steep cliffs. With the grace of a seasoned mountaineer, it scaled heights surpassing the $400 mark—an impressive feat captured on 18 distinct occasions between January 24, 2024, and February 20, 2024. This series of peaks in Microsoft's financial landscape is not merely a number—it's a testament to the company's robust performance as the new year unfolded.\n", + "\n", + "### The Days of Triumph\n", + "\n", + "Investors and market enthusiasts watched with bated breath as Microsoft shares rose, cresting $400 with the ease of a tech giant aware of its prowess. These moments of triumph were not fleeting; they laid a strong foundation for what might be interpreted as bullish days ahead:\n", + "\n", + "- The stock broke the $400 barrier, acting as both a support level in traders' minds and a signal of the company's market strength.\n", + "- The pinnacle of this period was reached on February 9, 2024, with the stock closing at an all-time daily high of $419.77—a beacon of the company's financial vitality.\n", + "\n", + "### Deciphering the Ascent\n", + "\n", + "What fueled this upward drive? When a stock like Microsoft's sails beyond a threshold such as $400, the reasons may be as multifaceted as the company's own product line:\n", + "\n", + "- **Positive Market Sentiment:** The market's general mood can often tip the scales. If investors are optimistic, even the sky isn't a limit. \n", + "- **Strong Financial Results:** Like a report card that every investor scrutinizes, Microsoft's financials must have shone, signaling health and profitability.\n", + "- **Favorable Company News:** An announcement, a groundbreaking product release, or an influential acquisition might have been the wind beneath the company's wings.\n", + "\n", + "### A Year in the Making\n", + "\n", + "While the early elevation of Microsoft's stock price sets a tone of confidence, it is but a chapter in the annals of an entire year's performance. To truly understand the narrative of Microsoft in 2024, one would need to turn every page of the calendar, scrutinizing the ebb and flow of its stock with a keen eye on the events that sway the market.\n", + "\n", + "In the splendor of its early achievements, Microsoft seemed to be painting a hopeful picture for 2024. Yet, the true masterpiece of its annual performance will only come into full view with time and the unfolding of the market's complex tapestry. Whether the early rise will be a prelude to a sustained ascent or a peak before a valley remains a story yet to be told.\n", + "\n", + "In conclusion, the days where Microsoft's stock stood tall above $400 signal not just a strong start but also pose intriguing questions about the sustainability of such performance. As history has often shown us, the stock market is a realm of intrigue, where each fluctuation tells a story, and every peak is a prelude to the next great adventure in finance. Stay tuned, for the market writes its own epic, and Microsoft's stock is but one of its many protagonists.\n", + "The provided content appears to be a financial commentary or news article discussing the performance of Microsoft's stock in early 2024. It provides factual information about Microsoft's stock prices and offers speculative insight into the possible reasons behind the stock's performance, as well as caution regarding the uncertainty of future market behavior.\n", + "\n", + "Upon reviewing the content, here are the considerations to ensure compliance and the absence of harmful elements:\n", + "\n", + "1. **Accuracy of Information**: The article mentions specific dates and stock prices which need to be accurate and verifiable. Falsifying or misrepresenting financial data could lead to serious regulatory violations and mislead investors.\n", + "\n", + "2. **Forward-Looking Statements**: When discussing potential future performance (\"bullish days ahead\", \"whether the early rise will be a prelude to a sustained ascent\"), it should be clear to readers that these are speculative and not guarantees. It is prudent to include disclaimers regarding forward-looking statements to avoid misleading investors per securities law.\n", + "\n", + "3. **Investment Advice**: The article should not be construed as giving specific investment advice unless it is coming from a licensed professional authorized to do so. Ensure that the language used does not directly endorse buying or selling specific securities.\n", + "\n", + "4. **Fairness and Balance**: The article should present a balanced view, acknowledging both potential upsides and risks. It should avoid overemphasizing the positive aspects without considering factors that could negatively impact the stock’s future performance.\n", + "\n", + "5. **Use of Metaphors**: The article employs metaphors (e.g., \"akin to a valiant climber\", \"the market's steep cliffs\") which are acceptable as long as they don't confuse or mislead the readers about the financial realities being discussed.\n", + "\n", + "6. **Promotional Content**: Ensure that the article is not serving as promotional material for Microsoft or any associated entities without appropriate disclosures.\n", + "\n", + "7. **Conflicts of Interest**: Disclose any potential conflicts of interest that the publisher of the content may have with Microsoft.\n", + "\n", + "8. **Regulatory Compliance**: Verify that the content aligns with the relevant regulatory requirements, such as those set forth by the SEC or other financial regulatory authorities that govern financial reporting and communications.\n", + "\n", + "9. **Intellectual Property**: Verify that any content has not infringed on the intellectual property rights of others.\n", + "\n", + "10. **Market Manipulation**: Ensure that there are no attempts to manipulate the market by presenting information in a biased or coercive manner.\n", + "\n", + "From the provided content, there don't appear to be explicit violations; however, thorough fact-checking and the addition of appropriate disclaimers would be needed to fully comply with regulations. The speculative and forward-looking statements should be clearly marked as such to avoid any misunderstandings regarding the nature of the content. \n", + "\n", + "It is advisable for financial articles of this nature to remind readers to perform their own due diligence and remind them that past performance is not indicative of future results. If all these aspects are thoroughly checked and addressed, the content could be considered compliant and free from harmful elements.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mWriter\u001b[0m (to Assistant_1):\n", + "\n", + "# Microsoft's High-Flying Start to 2024: Unpacking the Stock's Soar Above $400\n", + "\n", + "In the frosty embrace of early 2024, Microsoft Corp.'s stock price emerged not merely as a figure on screens but as a prevailing narrative of growth, triumphing over the hallowed $400 threshold on 18 noteworthy days from January 24 to February 20. This remarkable trend in Microsoft's financial journey reflects a vigor within the tech behemoth that caught the eyes of investors and marked a roaring start to the year.\n", + "\n", + "## Peaks of Prosperity: The Journey Beyond $400\n", + "\n", + "The financial markets, akin to an ever-watching audience, witnessed Microsoft's shares take flight, each venture past the $400 mark underscoring the firm's towering presence and industry stamina. Not content with mere flirtation, Microsoft's shares danced above and beyond, achieving their zenith on February 9 with an all-time daily high closing at $419.77.\n", + "\n", + "## Behind the Rally: Connecting the Dots\n", + "\n", + "What catapults a stock to such lofty heights? Microsoft's enviable ascent can be distilled into a concoction of factors, each playing its part in the grander scheme of market dynamics:\n", + "\n", + "- **A Wave of Optimism:** The collective mood of the market can act as a powerful catalyst, and in those early weeks, investor spirits were undeniably high.\n", + "- **A Healthy Financial Report Card:** Microsoft's financial results likely gleamed with a favorable sheen, signaling to the world its continued prosperity and growth potential.\n", + "- **Strategic Moves and Innovations:** Perhaps a strategic pivot, an innovative breakthrough, or a game-changing partnership filled Microsoft's sails with bullish winds.\n", + "\n", + "## The Broader Canvas: A Year in Perspective\n", + "\n", + "Any praise for Microsoft's bright start to 2024 cries out for context — a single snapshot is but a piece of the mosaic that is an entire fiscal year. To paint a complete picture of the company's performance, one must observe the stock across all seasons, taking note of the intertwined factors that nudge its trajectory this way or that.\n", + "\n", + "In the burgeoning chapters of 2024, Microsoft has sketched an early outline of promise and potential. Yet the masterpiece of its full-year performance will command patience, as time unveils the broader strokes and colors that define the market's intricate canvas. Can Microsoft maintain its high-altitude flight, or will it encounter turbulence? That remains a saga in progress.\n", + "\n", + "In sum, while the days where Microsoft's valuation soared high are markers of a strong outset, they are but initial brushstrokes in the grand financial fresco of 2024. As we know all too well, the stock market is a realm steeped in drama and anticipation, where each shift crafts a new narrative, and every summit signals the beginning of a new voyage in the epic odyssey of finance.\n", + "\n", + "Investors and spectators alike, let us watch with tempered excitement and a measured gaze. For the tale of Microsoft's stock is ongoing, and each chapter holds the promise of new lessons in the art of market navigation.\n", + "\n", + "---\n", + "\n", + "_Disclaimer: This article is for informational purposes only and does not constitute financial advice, endorsement, or recommendations. The performance reflected does not guarantee future results. Investors are advised to conduct their own due diligence or consult with a financial professional before making investment decisions. The author and publisher do not hold any positions in Microsoft stock at the time of writing and have disclosed no relevant affiliations beyond their academic appointment._\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mAssistant_1\u001b[0m (to User):\n", + "\n", + "# Microsoft's High-Flying Start to 2024: Unpacking the Stock's Soar Above $400\n", + "\n", + "In the frosty embrace of early 2024, Microsoft Corp.'s stock price emerged not merely as a figure on screens but as a prevailing narrative of growth, triumphing over the hallowed $400 threshold on 18 noteworthy days from January 24 to February 20. This remarkable trend in Microsoft's financial journey reflects a vigor within the tech behemoth that caught the eyes of investors and marked a roaring start to the year.\n", + "\n", + "## Peaks of Prosperity: The Journey Beyond $400\n", + "\n", + "The financial markets, akin to an ever-watching audience, witnessed Microsoft's shares take flight, each venture past the $400 mark underscoring the firm's towering presence and industry stamina. Not content with mere flirtation, Microsoft's shares danced above and beyond, achieving their zenith on February 9 with an all-time daily high closing at $419.77.\n", + "\n", + "## Behind the Rally: Connecting the Dots\n", + "\n", + "What catapults a stock to such lofty heights? Microsoft's enviable ascent can be distilled into a concoction of factors, each playing its part in the grander scheme of market dynamics:\n", + "\n", + "- **A Wave of Optimism:** The collective mood of the market can act as a powerful catalyst, and in those early weeks, investor spirits were undeniably high.\n", + "- **A Healthy Financial Report Card:** Microsoft's financial results likely gleamed with a favorable sheen, signaling to the world its continued prosperity and growth potential.\n", + "- **Strategic Moves and Innovations:** Perhaps a strategic pivot, an innovative breakthrough, or a game-changing partnership filled Microsoft's sails with bullish winds.\n", + "\n", + "## The Broader Canvas: A Year in Perspective\n", + "\n", + "Any praise for Microsoft's bright start to 2024 cries out for context — a single snapshot is but a piece of the mosaic that is an entire fiscal year. To paint a complete picture of the company's performance, one must observe the stock across all seasons, taking note of the intertwined factors that nudge its trajectory this way or that.\n", + "\n", + "In the burgeoning chapters of 2024, Microsoft has sketched an early outline of promise and potential. Yet the masterpiece of its full-year performance will command patience, as time unveils the broader strokes and colors that define the market's intricate canvas. Can Microsoft maintain its high-altitude flight, or will it encounter turbulence? That remains a saga in progress.\n", + "\n", + "In sum, while the days where Microsoft's valuation soared high are markers of a strong outset, they are but initial brushstrokes in the grand financial fresco of 2024. As we know all too well, the stock market is a realm steeped in drama and anticipation, where each shift crafts a new narrative, and every summit signals the beginning of a new voyage in the epic odyssey of finance.\n", + "\n", + "Investors and spectators alike, let us watch with tempered excitement and a measured gaze. For the tale of Microsoft's stock is ongoing, and each chapter holds the promise of new lessons in the art of market navigation.\n", + "\n", + "---\n", + "\n", + "_Disclaimer: This article is for informational purposes only and does not constitute financial advice, endorsement, or recommendations. The performance reflected does not guarantee future results. Investors are advised to conduct their own due diligence or consult with a financial professional before making investment decisions. The author and publisher do not hold any positions in Microsoft stock at the time of writing and have disclosed no relevant affiliations beyond their academic appointment._\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[34mStart a new chat with the following message: \n", + "Investigate possible reasons for the performance.\n", + "\n", + "With the following carryover: \n", + "# Microsoft's High-Flying Start to 2024: Unpacking the Stock's Soar Above $400\n", + "\n", + "In the frosty embrace of early 2024, Microsoft Corp.'s stock price emerged not merely as a figure on screens but as a prevailing narrative of growth, triumphing over the hallowed $400 threshold on 18 noteworthy days from January 24 to February 20. This remarkable trend in Microsoft's financial journey reflects a vigor within the tech behemoth that caught the eyes of investors and marked a roaring start to the year.\n", + "\n", + "## Peaks of Prosperity: The Journey Beyond $400\n", + "\n", + "The financial markets, akin to an ever-watching audience, witnessed Microsoft's shares take flight, each venture past the $400 mark underscoring the firm's towering presence and industry stamina. Not content with mere flirtation, Microsoft's shares danced above and beyond, achieving their zenith on February 9 with an all-time daily high closing at $419.77.\n", + "\n", + "## Behind the Rally: Connecting the Dots\n", + "\n", + "What catapults a stock to such lofty heights? Microsoft's enviable ascent can be distilled into a concoction of factors, each playing its part in the grander scheme of market dynamics:\n", + "\n", + "- **A Wave of Optimism:** The collective mood of the market can act as a powerful catalyst, and in those early weeks, investor spirits were undeniably high.\n", + "- **A Healthy Financial Report Card:** Microsoft's financial results likely gleamed with a favorable sheen, signaling to the world its continued prosperity and growth potential.\n", + "- **Strategic Moves and Innovations:** Perhaps a strategic pivot, an innovative breakthrough, or a game-changing partnership filled Microsoft's sails with bullish winds.\n", + "\n", + "## The Broader Canvas: A Year in Perspective\n", + "\n", + "Any praise for Microsoft's bright start to 2024 cries out for context — a single snapshot is but a piece of the mosaic that is an entire fiscal year. To paint a complete picture of the company's performance, one must observe the stock across all seasons, taking note of the intertwined factors that nudge its trajectory this way or that.\n", + "\n", + "In the burgeoning chapters of 2024, Microsoft has sketched an early outline of promise and potential. Yet the masterpiece of its full-year performance will command patience, as time unveils the broader strokes and colors that define the market's intricate canvas. Can Microsoft maintain its high-altitude flight, or will it encounter turbulence? That remains a saga in progress.\n", + "\n", + "In sum, while the days where Microsoft's valuation soared high are markers of a strong outset, they are but initial brushstrokes in the grand financial fresco of 2024. As we know all too well, the stock market is a realm steeped in drama and anticipation, where each shift crafts a new narrative, and every summit signals the beginning of a new voyage in the epic odyssey of finance.\n", + "\n", + "Investors and spectators alike, let us watch with tempered excitement and a measured gaze. For the tale of Microsoft's stock is ongoing, and each chapter holds the promise of new lessons in the art of market navigation.\n", + "\n", + "---\n", + "\n", + "_Disclaimer: This article is for informational purposes only and does not constitute financial advice, endorsement, or recommendations. The performance reflected does not guarantee future results. Investors are advised to conduct their own due diligence or consult with a financial professional before making investment decisions. The author and publisher do not hold any positions in Microsoft stock at the time of writing and have disclosed no relevant affiliations beyond their academic appointment._\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", + "\u001b[33mUser\u001b[0m (to Assistant_2):\n", + "\n", + "Investigate possible reasons for the performance.\n", + "Context: \n", + "# Microsoft's High-Flying Start to 2024: Unpacking the Stock's Soar Above $400\n", + "\n", + "In the frosty embrace of early 2024, Microsoft Corp.'s stock price emerged not merely as a figure on screens but as a prevailing narrative of growth, triumphing over the hallowed $400 threshold on 18 noteworthy days from January 24 to February 20. This remarkable trend in Microsoft's financial journey reflects a vigor within the tech behemoth that caught the eyes of investors and marked a roaring start to the year.\n", + "\n", + "## Peaks of Prosperity: The Journey Beyond $400\n", + "\n", + "The financial markets, akin to an ever-watching audience, witnessed Microsoft's shares take flight, each venture past the $400 mark underscoring the firm's towering presence and industry stamina. Not content with mere flirtation, Microsoft's shares danced above and beyond, achieving their zenith on February 9 with an all-time daily high closing at $419.77.\n", + "\n", + "## Behind the Rally: Connecting the Dots\n", + "\n", + "What catapults a stock to such lofty heights? Microsoft's enviable ascent can be distilled into a concoction of factors, each playing its part in the grander scheme of market dynamics:\n", + "\n", + "- **A Wave of Optimism:** The collective mood of the market can act as a powerful catalyst, and in those early weeks, investor spirits were undeniably high.\n", + "- **A Healthy Financial Report Card:** Microsoft's financial results likely gleamed with a favorable sheen, signaling to the world its continued prosperity and growth potential.\n", + "- **Strategic Moves and Innovations:** Perhaps a strategic pivot, an innovative breakthrough, or a game-changing partnership filled Microsoft's sails with bullish winds.\n", + "\n", + "## The Broader Canvas: A Year in Perspective\n", + "\n", + "Any praise for Microsoft's bright start to 2024 cries out for context — a single snapshot is but a piece of the mosaic that is an entire fiscal year. To paint a complete picture of the company's performance, one must observe the stock across all seasons, taking note of the intertwined factors that nudge its trajectory this way or that.\n", + "\n", + "In the burgeoning chapters of 2024, Microsoft has sketched an early outline of promise and potential. Yet the masterpiece of its full-year performance will command patience, as time unveils the broader strokes and colors that define the market's intricate canvas. Can Microsoft maintain its high-altitude flight, or will it encounter turbulence? That remains a saga in progress.\n", + "\n", + "In sum, while the days where Microsoft's valuation soared high are markers of a strong outset, they are but initial brushstrokes in the grand financial fresco of 2024. As we know all too well, the stock market is a realm steeped in drama and anticipation, where each shift crafts a new narrative, and every summit signals the beginning of a new voyage in the epic odyssey of finance.\n", + "\n", + "Investors and spectators alike, let us watch with tempered excitement and a measured gaze. For the tale of Microsoft's stock is ongoing, and each chapter holds the promise of new lessons in the art of market navigation.\n", + "\n", + "---\n", + "\n", + "_Disclaimer: This article is for informational purposes only and does not constitute financial advice, endorsement, or recommendations. The performance reflected does not guarantee future results. Investors are advised to conduct their own due diligence or consult with a financial professional before making investment decisions. The author and publisher do not hold any positions in Microsoft stock at the time of writing and have disclosed no relevant affiliations beyond their academic appointment._\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mAssistant_2\u001b[0m (to User):\n", + "\n", + "To investigate the reasons for Microsoft's stock performance, we can look at financial reports, market news, and actions taken by the company during the period mentioned. These might include earnings reports, major announcements from the company (such as product launches or acquisitions), and overall market trends.\n", + "\n", + "Step 1: We will collect Microsoft's latest financial report as it is likely to play a significant role in stock performance. These reports are typically available on the company's investor relations page. \n", + "\n", + "Step 2: Next, we will search for any significant news or press releases from Microsoft that could have influenced investor sentiment. We'll look specifically around the dates mentioned for product announcements, acquisitions, strategic partnerships, or other corporate actions.\n", + "\n", + "Step 3: We will also take into account the general market conditions during this time. For example, if the tech sector or the stock market as a whole was experiencing a bullish trend, this could contribute to the surge in Microsoft's stock price.\n", + "\n", + "Since I can provide you with the Python code to scrape information from the web, would you like to proceed with downloading and inspecting Microsoft's latest earnings report, or should we search for news articles and press releases pertaining to Microsoft in the specified date range? Please note that some information might not be available via scraping due to paywalls or the need for subscriptions.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mUser\u001b[0m (to Assistant_2):\n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mAssistant_2\u001b[0m (to User):\n", + "\n", + "It seems there was no specific request made following my explanation of the steps to investigate Microsoft's stock performance. If you would like to proceed with any of these steps and need assistance in the form of Python code, please let me know the particular action you would like to take:\n", + "\n", + "1. Download and inspect Microsoft's latest earnings report.\n", + "2. Search for news articles and press releases about Microsoft in the specified date range.\n", + "3. Observe overall market trends during the specified date range.\n", + "\n", + "Choose one of the options, or specify another action you'd like to take, and I'll provide you with the relevant Python code to help you with your investigation.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mUser\u001b[0m (to Assistant_2):\n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mAssistant_2\u001b[0m (to User):\n", + "\n", + "As there has been no input, I will assume you might want to start with collecting Microsoft's latest financial report, which is likely publicly available and could reveal insights into their strong stock performance. To do this, you usually visit the investor relations section of the Microsoft website.\n", + "\n", + "However, I must remind you that as an AI, I currently don’t have the ability to browse the internet in real-time or directly access web content. But if you were to gather information yourself, you would typically go to Microsoft's official website, navigate to the \"Investor Relations\" section, and look for the most recent quarterly earnings report, usually available as a PDF or in an earnings call transcript.\n", + "\n", + "Afterward, you would inspect this document for key financial metrics such as revenue, net income, earnings per share, and guidance for future performance, as well as any commentary on strategic initiatives that might have influenced investor sentiment.\n", + "\n", + "If you had a different action in mind or need assistance with another request, please let me know, and I can provide you with the appropriate guidance.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mUser\u001b[0m (to Assistant_2):\n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mAssistant_2\u001b[0m (to User):\n", + "\n", + "Since there hasn't been a specific request or action provided, it's difficult to assist further with the investigation into Microsoft's stock performance. To help you efficiently, you'll need to provide clear instructions or a request for code related to one of the following:\n", + "\n", + "1. Downloading and analyzing Microsoft's latest financial earnings report.\n", + "2. Searching for Microsoft's recent news articles or press releases within a certain date range.\n", + "3. Reviewing overall stock market trends that might affect Microsoft's stock performance.\n", + "\n", + "If you decide how you'd like to proceed or if you have another inquiry, please provide the specific details or instructions, and I'll be glad to help with your investigation by providing the necessary assistance or Python code.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mUser\u001b[0m (to Assistant_2):\n", + "\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mAssistant_2\u001b[0m (to User):\n", + "\n", + "It seems there is still no specific directive to continue the investigation into Microsoft's stock performance. If you require my assistance in the future, whether it's with gathering financial reports, searching for news, analyzing market trends, or any other form of inquiry, please provide clear instructions, and I'll be ready to help.\n", + "\n", + "For now, without further information or a request, I will consider this task concluded. If you have any questions later on, feel free to reach out. \n", + "\n", + "TERMINATE\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "data": { + "text/plain": [ + "[ChatResult(chat_history=[{'content': 'On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.', 'role': 'assistant'}, {'content': \"# Microsoft's High-Flying Start to 2024: Unpacking the Stock's Soar Above $400\\n\\nIn the frosty embrace of early 2024, Microsoft Corp.'s stock price emerged not merely as a figure on screens but as a prevailing narrative of growth, triumphing over the hallowed $400 threshold on 18 noteworthy days from January 24 to February 20. This remarkable trend in Microsoft's financial journey reflects a vigor within the tech behemoth that caught the eyes of investors and marked a roaring start to the year.\\n\\n## Peaks of Prosperity: The Journey Beyond $400\\n\\nThe financial markets, akin to an ever-watching audience, witnessed Microsoft's shares take flight, each venture past the $400 mark underscoring the firm's towering presence and industry stamina. Not content with mere flirtation, Microsoft's shares danced above and beyond, achieving their zenith on February 9 with an all-time daily high closing at $419.77.\\n\\n## Behind the Rally: Connecting the Dots\\n\\nWhat catapults a stock to such lofty heights? Microsoft's enviable ascent can be distilled into a concoction of factors, each playing its part in the grander scheme of market dynamics:\\n\\n- **A Wave of Optimism:** The collective mood of the market can act as a powerful catalyst, and in those early weeks, investor spirits were undeniably high.\\n- **A Healthy Financial Report Card:** Microsoft's financial results likely gleamed with a favorable sheen, signaling to the world its continued prosperity and growth potential.\\n- **Strategic Moves and Innovations:** Perhaps a strategic pivot, an innovative breakthrough, or a game-changing partnership filled Microsoft's sails with bullish winds.\\n\\n## The Broader Canvas: A Year in Perspective\\n\\nAny praise for Microsoft's bright start to 2024 cries out for context — a single snapshot is but a piece of the mosaic that is an entire fiscal year. To paint a complete picture of the company's performance, one must observe the stock across all seasons, taking note of the intertwined factors that nudge its trajectory this way or that.\\n\\nIn the burgeoning chapters of 2024, Microsoft has sketched an early outline of promise and potential. Yet the masterpiece of its full-year performance will command patience, as time unveils the broader strokes and colors that define the market's intricate canvas. Can Microsoft maintain its high-altitude flight, or will it encounter turbulence? That remains a saga in progress.\\n\\nIn sum, while the days where Microsoft's valuation soared high are markers of a strong outset, they are but initial brushstrokes in the grand financial fresco of 2024. As we know all too well, the stock market is a realm steeped in drama and anticipation, where each shift crafts a new narrative, and every summit signals the beginning of a new voyage in the epic odyssey of finance.\\n\\nInvestors and spectators alike, let us watch with tempered excitement and a measured gaze. For the tale of Microsoft's stock is ongoing, and each chapter holds the promise of new lessons in the art of market navigation.\\n\\n---\\n\\n_Disclaimer: This article is for informational purposes only and does not constitute financial advice, endorsement, or recommendations. The performance reflected does not guarantee future results. Investors are advised to conduct their own due diligence or consult with a financial professional before making investment decisions. The author and publisher do not hold any positions in Microsoft stock at the time of writing and have disclosed no relevant affiliations beyond their academic appointment._\", 'role': 'user'}], summary=\"# Microsoft's High-Flying Start to 2024: Unpacking the Stock's Soar Above $400\\n\\nIn the frosty embrace of early 2024, Microsoft Corp.'s stock price emerged not merely as a figure on screens but as a prevailing narrative of growth, triumphing over the hallowed $400 threshold on 18 noteworthy days from January 24 to February 20. This remarkable trend in Microsoft's financial journey reflects a vigor within the tech behemoth that caught the eyes of investors and marked a roaring start to the year.\\n\\n## Peaks of Prosperity: The Journey Beyond $400\\n\\nThe financial markets, akin to an ever-watching audience, witnessed Microsoft's shares take flight, each venture past the $400 mark underscoring the firm's towering presence and industry stamina. Not content with mere flirtation, Microsoft's shares danced above and beyond, achieving their zenith on February 9 with an all-time daily high closing at $419.77.\\n\\n## Behind the Rally: Connecting the Dots\\n\\nWhat catapults a stock to such lofty heights? Microsoft's enviable ascent can be distilled into a concoction of factors, each playing its part in the grander scheme of market dynamics:\\n\\n- **A Wave of Optimism:** The collective mood of the market can act as a powerful catalyst, and in those early weeks, investor spirits were undeniably high.\\n- **A Healthy Financial Report Card:** Microsoft's financial results likely gleamed with a favorable sheen, signaling to the world its continued prosperity and growth potential.\\n- **Strategic Moves and Innovations:** Perhaps a strategic pivot, an innovative breakthrough, or a game-changing partnership filled Microsoft's sails with bullish winds.\\n\\n## The Broader Canvas: A Year in Perspective\\n\\nAny praise for Microsoft's bright start to 2024 cries out for context — a single snapshot is but a piece of the mosaic that is an entire fiscal year. To paint a complete picture of the company's performance, one must observe the stock across all seasons, taking note of the intertwined factors that nudge its trajectory this way or that.\\n\\nIn the burgeoning chapters of 2024, Microsoft has sketched an early outline of promise and potential. Yet the masterpiece of its full-year performance will command patience, as time unveils the broader strokes and colors that define the market's intricate canvas. Can Microsoft maintain its high-altitude flight, or will it encounter turbulence? That remains a saga in progress.\\n\\nIn sum, while the days where Microsoft's valuation soared high are markers of a strong outset, they are but initial brushstrokes in the grand financial fresco of 2024. As we know all too well, the stock market is a realm steeped in drama and anticipation, where each shift crafts a new narrative, and every summit signals the beginning of a new voyage in the epic odyssey of finance.\\n\\nInvestors and spectators alike, let us watch with tempered excitement and a measured gaze. For the tale of Microsoft's stock is ongoing, and each chapter holds the promise of new lessons in the art of market navigation.\\n\\n---\\n\\n_Disclaimer: This article is for informational purposes only and does not constitute financial advice, endorsement, or recommendations. The performance reflected does not guarantee future results. Investors are advised to conduct their own due diligence or consult with a financial professional before making investment decisions. The author and publisher do not hold any positions in Microsoft stock at the time of writing and have disclosed no relevant affiliations beyond their academic appointment._\", cost=({'total_cost': 0}, {'total_cost': 0}), human_input=[]),\n", + " ChatResult(chat_history=[{'content': \"Investigate possible reasons for the performance.\\nContext: \\n# Microsoft's High-Flying Start to 2024: Unpacking the Stock's Soar Above $400\\n\\nIn the frosty embrace of early 2024, Microsoft Corp.'s stock price emerged not merely as a figure on screens but as a prevailing narrative of growth, triumphing over the hallowed $400 threshold on 18 noteworthy days from January 24 to February 20. This remarkable trend in Microsoft's financial journey reflects a vigor within the tech behemoth that caught the eyes of investors and marked a roaring start to the year.\\n\\n## Peaks of Prosperity: The Journey Beyond $400\\n\\nThe financial markets, akin to an ever-watching audience, witnessed Microsoft's shares take flight, each venture past the $400 mark underscoring the firm's towering presence and industry stamina. Not content with mere flirtation, Microsoft's shares danced above and beyond, achieving their zenith on February 9 with an all-time daily high closing at $419.77.\\n\\n## Behind the Rally: Connecting the Dots\\n\\nWhat catapults a stock to such lofty heights? Microsoft's enviable ascent can be distilled into a concoction of factors, each playing its part in the grander scheme of market dynamics:\\n\\n- **A Wave of Optimism:** The collective mood of the market can act as a powerful catalyst, and in those early weeks, investor spirits were undeniably high.\\n- **A Healthy Financial Report Card:** Microsoft's financial results likely gleamed with a favorable sheen, signaling to the world its continued prosperity and growth potential.\\n- **Strategic Moves and Innovations:** Perhaps a strategic pivot, an innovative breakthrough, or a game-changing partnership filled Microsoft's sails with bullish winds.\\n\\n## The Broader Canvas: A Year in Perspective\\n\\nAny praise for Microsoft's bright start to 2024 cries out for context — a single snapshot is but a piece of the mosaic that is an entire fiscal year. To paint a complete picture of the company's performance, one must observe the stock across all seasons, taking note of the intertwined factors that nudge its trajectory this way or that.\\n\\nIn the burgeoning chapters of 2024, Microsoft has sketched an early outline of promise and potential. Yet the masterpiece of its full-year performance will command patience, as time unveils the broader strokes and colors that define the market's intricate canvas. Can Microsoft maintain its high-altitude flight, or will it encounter turbulence? That remains a saga in progress.\\n\\nIn sum, while the days where Microsoft's valuation soared high are markers of a strong outset, they are but initial brushstrokes in the grand financial fresco of 2024. As we know all too well, the stock market is a realm steeped in drama and anticipation, where each shift crafts a new narrative, and every summit signals the beginning of a new voyage in the epic odyssey of finance.\\n\\nInvestors and spectators alike, let us watch with tempered excitement and a measured gaze. For the tale of Microsoft's stock is ongoing, and each chapter holds the promise of new lessons in the art of market navigation.\\n\\n---\\n\\n_Disclaimer: This article is for informational purposes only and does not constitute financial advice, endorsement, or recommendations. The performance reflected does not guarantee future results. Investors are advised to conduct their own due diligence or consult with a financial professional before making investment decisions. The author and publisher do not hold any positions in Microsoft stock at the time of writing and have disclosed no relevant affiliations beyond their academic appointment._\", 'role': 'assistant'}, {'content': \"To investigate the reasons for Microsoft's stock performance, we can look at financial reports, market news, and actions taken by the company during the period mentioned. These might include earnings reports, major announcements from the company (such as product launches or acquisitions), and overall market trends.\\n\\nStep 1: We will collect Microsoft's latest financial report as it is likely to play a significant role in stock performance. These reports are typically available on the company's investor relations page. \\n\\nStep 2: Next, we will search for any significant news or press releases from Microsoft that could have influenced investor sentiment. We'll look specifically around the dates mentioned for product announcements, acquisitions, strategic partnerships, or other corporate actions.\\n\\nStep 3: We will also take into account the general market conditions during this time. For example, if the tech sector or the stock market as a whole was experiencing a bullish trend, this could contribute to the surge in Microsoft's stock price.\\n\\nSince I can provide you with the Python code to scrape information from the web, would you like to proceed with downloading and inspecting Microsoft's latest earnings report, or should we search for news articles and press releases pertaining to Microsoft in the specified date range? Please note that some information might not be available via scraping due to paywalls or the need for subscriptions.\", 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': \"It seems there was no specific request made following my explanation of the steps to investigate Microsoft's stock performance. If you would like to proceed with any of these steps and need assistance in the form of Python code, please let me know the particular action you would like to take:\\n\\n1. Download and inspect Microsoft's latest earnings report.\\n2. Search for news articles and press releases about Microsoft in the specified date range.\\n3. Observe overall market trends during the specified date range.\\n\\nChoose one of the options, or specify another action you'd like to take, and I'll provide you with the relevant Python code to help you with your investigation.\", 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': 'As there has been no input, I will assume you might want to start with collecting Microsoft\\'s latest financial report, which is likely publicly available and could reveal insights into their strong stock performance. To do this, you usually visit the investor relations section of the Microsoft website.\\n\\nHowever, I must remind you that as an AI, I currently don’t have the ability to browse the internet in real-time or directly access web content. But if you were to gather information yourself, you would typically go to Microsoft\\'s official website, navigate to the \"Investor Relations\" section, and look for the most recent quarterly earnings report, usually available as a PDF or in an earnings call transcript.\\n\\nAfterward, you would inspect this document for key financial metrics such as revenue, net income, earnings per share, and guidance for future performance, as well as any commentary on strategic initiatives that might have influenced investor sentiment.\\n\\nIf you had a different action in mind or need assistance with another request, please let me know, and I can provide you with the appropriate guidance.', 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': \"Since there hasn't been a specific request or action provided, it's difficult to assist further with the investigation into Microsoft's stock performance. To help you efficiently, you'll need to provide clear instructions or a request for code related to one of the following:\\n\\n1. Downloading and analyzing Microsoft's latest financial earnings report.\\n2. Searching for Microsoft's recent news articles or press releases within a certain date range.\\n3. Reviewing overall stock market trends that might affect Microsoft's stock performance.\\n\\nIf you decide how you'd like to proceed or if you have another inquiry, please provide the specific details or instructions, and I'll be glad to help with your investigation by providing the necessary assistance or Python code.\", 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': \"It seems there is still no specific directive to continue the investigation into Microsoft's stock performance. If you require my assistance in the future, whether it's with gathering financial reports, searching for news, analyzing market trends, or any other form of inquiry, please provide clear instructions, and I'll be ready to help.\\n\\nFor now, without further information or a request, I will consider this task concluded. If you have any questions later on, feel free to reach out. \\n\\nTERMINATE\", 'role': 'user'}], summary=\"It seems there is still no specific directive to continue the investigation into Microsoft's stock performance. If you require my assistance in the future, whether it's with gathering financial reports, searching for news, analyzing market trends, or any other form of inquiry, please provide clear instructions, and I'll be ready to help.\\n\\nFor now, without further information or a request, I will consider this task concluded. If you have any questions later on, feel free to reach out. \\n\\n\", cost=({'total_cost': 0.29031, 'gpt-4': {'cost': 0.29031, 'prompt_tokens': 7995, 'completion_tokens': 841, 'total_tokens': 8836}}, {'total_cost': 0.29031, 'gpt-4': {'cost': 0.29031, 'prompt_tokens': 7995, 'completion_tokens': 841, 'total_tokens': 8836}}), human_input=[])]" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "def writing_message(recipient, messages, sender, config):\n", + " return f\"Polish the content to make an engaging and nicely formatted blog post. \\n\\n {recipient.chat_messages_for_summary(sender)[-1]['content']}\"\n", + "\n", + "\n", + "nested_chat_queue = [\n", + " {\"recipient\": manager, \"summary_method\": \"reflection_with_llm\"},\n", + " {\"recipient\": writer, \"message\": writing_message, \"summary_method\": \"last_msg\", \"max_turns\": 1},\n", + " {\"recipient\": reviewer, \"message\": \"Review the content provided.\", \"summary_method\": \"last_msg\", \"max_turns\": 1},\n", + " {\"recipient\": writer, \"message\": writing_message, \"summary_method\": \"last_msg\", \"max_turns\": 1},\n", + "]\n", + "assistant_1.register_nested_chats(\n", + " [autogen.Agent, None],\n", + " nested_chat_queue,\n", + ")\n", + "# user.initiate_chat(assistant, message=tasks[0], max_turns=1)\n", + "\n", + "user.initiate_chats(\n", + " [\n", + " {\"recipient\": assistant_1, \"message\": tasks[0], \"max_turns\": 1, \"summary_method\": \"last_msg\"},\n", + " {\"recipient\": assistant_2, \"message\": tasks[1]},\n", + " ]\n", + ")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "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.10.13" + }, + "vscode": { + "interpreter": { + "hash": "949777d72b0d2535278d3dc13498b2535136f6dfe0678499012e853ee9abcab1" + } + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "state": { + "2d910cfd2d2a4fc49fc30fbbdc5576a7": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "454146d0f7224f038689031002906e6f": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HBoxModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HBoxModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HBoxView", + "box_style": "", + "children": [ + "IPY_MODEL_e4ae2b6f5a974fd4bafb6abb9d12ff26", + "IPY_MODEL_577e1e3cc4db4942b0883577b3b52755", + "IPY_MODEL_b40bdfb1ac1d4cffb7cefcb870c64d45" + ], + "layout": "IPY_MODEL_dc83c7bff2f241309537a8119dfc7555", + "tabbable": null, + "tooltip": null + } + }, + "577e1e3cc4db4942b0883577b3b52755": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "FloatProgressModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "FloatProgressModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "ProgressView", + "bar_style": "success", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_2d910cfd2d2a4fc49fc30fbbdc5576a7", + "max": 1, + "min": 0, + "orientation": "horizontal", + "style": "IPY_MODEL_74a6ba0c3cbc4051be0a83e152fe1e62", + "tabbable": null, + "tooltip": null, + "value": 1 + } + }, + "6086462a12d54bafa59d3c4566f06cb2": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "74a6ba0c3cbc4051be0a83e152fe1e62": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "ProgressStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "ProgressStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "bar_color": null, + "description_width": "" + } + }, + "7d3f3d9e15894d05a4d188ff4f466554": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "background": null, + "description_width": "", + "font_size": null, + "text_color": null + } + }, + "b40bdfb1ac1d4cffb7cefcb870c64d45": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HTMLView", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_f1355871cc6f4dd4b50d9df5af20e5c8", + "placeholder": "​", + "style": "IPY_MODEL_ca245376fd9f4354af6b2befe4af4466", + "tabbable": null, + "tooltip": null, + "value": " 1/1 [00:00<00:00, 44.69it/s]" + } + }, + "ca245376fd9f4354af6b2befe4af4466": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "StyleView", + "background": null, + "description_width": "", + "font_size": null, + "text_color": null + } + }, + "dc83c7bff2f241309537a8119dfc7555": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "e4ae2b6f5a974fd4bafb6abb9d12ff26": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "2.0.0", + "model_name": "HTMLModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "2.0.0", + "_model_name": "HTMLModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "2.0.0", + "_view_name": "HTMLView", + "description": "", + "description_allow_html": false, + "layout": "IPY_MODEL_6086462a12d54bafa59d3c4566f06cb2", + "placeholder": "​", + "style": "IPY_MODEL_7d3f3d9e15894d05a4d188ff4f466554", + "tabbable": null, + "tooltip": null, + "value": "100%" + } + }, + "f1355871cc6f4dd4b50d9df5af20e5c8": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "2.0.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "2.0.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "2.0.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border_bottom": null, + "border_left": null, + "border_right": null, + "border_top": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + } + }, + "version_major": 2, + "version_minor": 0 + } + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/notebook/agentchat_nestedchat_Society_of_Mind.ipynb b/notebook/agentchat_nestedchat_Society_of_Mind.ipynb deleted file mode 100644 index a52ab47f9a4..00000000000 --- a/notebook/agentchat_nestedchat_Society_of_Mind.ipynb +++ /dev/null @@ -1,793 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "# Society of Minds with Nested Chats in AutoGen\n", - "\n", - "This notebook showcases how to use conversable agents in AutoGen to realize a Society of Mind (SoM) scenario, in which an agent runs a chat session with another agent as an inner-monologue. The idea of SoM and part of the example demonstrated in this notebook come from this PR: https://github.com/microsoft/autogen/pull/890\n", - " \n", - "\n", - "## Requirements\n", - "\n", - "AutoGen requires `Python>=3.8`. To run this notebook example, please install:\n", - "```bash\n", - "pip install pyautogen\n", - "```" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Society of Minds with Nested Chats" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "import autogen\n", - "\n", - "config_list = autogen.config_list_from_json(env_or_file=\"OAI_CONFIG_LIST\")\n", - "llm_config = {\"config_list\": config_list, \"cache_seed\": 44}\n", - "\n", - "financial_tasks = [\n", - " \"\"\"On which days this year was Microsoft Stock higher than $400? Put results in a table.\"\"\",\n", - " \"\"\"Investigate the possible reasons of the stock performance.\"\"\",\n", - "]\n", - "\n", - "writing_tasks = [\"\"\"Develop an engaging blog post using any information provided.\"\"\"]" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Construct a group chat manager to be used for the inner-monologue" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "assistant = autogen.AssistantAgent(\n", - " \"Inner-assistant\",\n", - " llm_config=llm_config,\n", - " is_termination_msg=lambda x: x.get(\"content\", \"\").find(\"TERMINATE\") >= 0,\n", - ")\n", - "\n", - "code_interpreter = autogen.UserProxyAgent(\n", - " \"Inner-code-interpreter\",\n", - " human_input_mode=\"NEVER\",\n", - " code_execution_config={\n", - " \"work_dir\": \"coding\",\n", - " \"use_docker\": False,\n", - " },\n", - " default_auto_reply=\"\",\n", - " is_termination_msg=lambda x: x.get(\"content\", \"\").find(\"TERMINATE\") >= 0,\n", - ")\n", - "\n", - "groupchat = autogen.GroupChat(\n", - " agents=[assistant, code_interpreter],\n", - " messages=[],\n", - " speaker_selection_method=\"round_robin\", # With two agents, this is equivalent to a 1:1 conversation.\n", - " allow_repeat_speaker=False,\n", - " max_round=8,\n", - ")\n", - "\n", - "manager = autogen.GroupChatManager(\n", - " groupchat=groupchat,\n", - " is_termination_msg=lambda x: x.get(\"content\", \"\").find(\"TERMINATE\") >= 0,\n", - " llm_config=llm_config,\n", - " code_execution_config={\n", - " \"work_dir\": \"coding\",\n", - " \"use_docker\": False,\n", - " },\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[33mUser\u001b[0m (to Financial_assistant_1):\n", - "\n", - "On which days this year was Microsoft Stock higher than $400? Put results in a table.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[34mStart a new chat with the following message: \n", - "On which days this year was Microsoft Stock higher than $400? Put results in a table.\n", - "\n", - "With the following carryover: \n", - "\u001b[0m\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[33mFinancial_assistant_1\u001b[0m (to chat_manager):\n", - "\n", - "On which days this year was Microsoft Stock higher than $400? Put results in a table.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", - "\n", - "To determine on which days Microsoft (ticker symbol: MSFT) stock was trading higher than $400 this year, I will use Python with the `pandas` library and the `yfinance` package to fetch historical stock price data from Yahoo Finance. To get this data, you must first ensure that `pandas` and `yfinance` are installed in your Python environment. If they're not installed, you can install them using pip.\n", - "\n", - "Here's a step-by-step plan we're going to follow:\n", - "\n", - "1. Import the required libraries.\n", - "2. Set up the ticker symbol for Microsoft.\n", - "3. Define the date range for \"this year\" until today.\n", - "4. Use yfinance to download the stock data for Microsoft.\n", - "5. Filter the data to find the days when the stock was higher than $400.\n", - "6. Print out the result in a tabular format.\n", - "\n", - "Please run the following code:\n", - "\n", - "```python\n", - "# filename: msft_stock_days_above_400.py\n", - "import pandas as pd\n", - "import yfinance as yf\n", - "from datetime import datetime\n", - "\n", - "# Ensure that pandas and yfinance are installed\n", - "try:\n", - " import pandas\n", - " import yfinance\n", - "except ImportError as e:\n", - " print(\"Please install pandas and yfinance using 'pip install pandas yfinance'\")\n", - " raise e\n", - "\n", - "def find_msft_days_above_400():\n", - " # Ticker symbol for Microsoft\n", - " ticker_symbol = \"MSFT\"\n", - " \n", - " # get the current year\n", - " current_year = datetime.now().year\n", - "\n", - " # Define the date range for this year until today\n", - " start_date = f\"{current_year}-01-01\"\n", - " end_date = datetime.now().strftime('%Y-%m-%d')\n", - "\n", - " # Download the stock price data\n", - " msft_data = yf.download(ticker_symbol, start=start_date, end=end_date)\n", - "\n", - " # Filter the data for days when the closing price was higher than $400\n", - " days_above_400 = msft_data[msft_data['Close'] > 400]\n", - "\n", - " # Check if there are any days that the closing price was above $400\n", - " if not days_above_400.empty:\n", - " # Print the result in a tabular format\n", - " print(days_above_400[['Close']].to_markdown())\n", - " else:\n", - " print(f\"No days in {current_year} where the Microsoft stock closed above $400.\")\n", - "\n", - "if __name__ == \"__main__\":\n", - " find_msft_days_above_400()\n", - "```\n", - "\n", - "Please save the above code to a file and run it using Python. It will print the days when Microsoft stock was higher than $400 this year, if any, in a markdown table format. If the stock has not surpassed that value this year, the script will tell you so.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[31m\n", - ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n", - "\u001b[33mInner-code-interpreter\u001b[0m (to chat_manager):\n", - "\n", - "exitcode: 1 (execution failed)\n", - "Code output: \n", - "msft_stock_days_above_400.py:2: DeprecationWarning: \n", - "Pyarrow will become a required dependency of pandas in the next major release of pandas (pandas 3.0),\n", - "(to allow more performant data types, such as the Arrow string type, and better interoperability with other libraries)\n", - "but was not found to be installed on your system.\n", - "If this would cause problems for you,\n", - "please provide us feedback at https://github.com/pandas-dev/pandas/issues/54466\n", - " \n", - " import pandas as pd\n", - "/Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages/yfinance/utils.py:775: FutureWarning: The 'unit' keyword in TimedeltaIndex construction is deprecated and will be removed in a future version. Use pd.to_timedelta instead.\n", - " df.index += _pd.TimedeltaIndex(dst_error_hours, 'h')\n", - "\n", - "[*********************100%%**********************] 1 of 1 completed\n", - "Traceback (most recent call last):\n", - " File \"/Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages/pandas/compat/_optional.py\", line 135, in import_optional_dependency\n", - " module = importlib.import_module(name)\n", - " File \"/Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/importlib/__init__.py\", line 126, in import_module\n", - " return _bootstrap._gcd_import(name[level:], package, level)\n", - " File \"\", line 1050, in _gcd_import\n", - " File \"\", line 1027, in _find_and_load\n", - " File \"\", line 1004, in _find_and_load_unlocked\n", - "ModuleNotFoundError: No module named 'tabulate'\n", - "\n", - "During handling of the above exception, another exception occurred:\n", - "\n", - "Traceback (most recent call last):\n", - " File \"msft_stock_days_above_400.py\", line 39, in \n", - " find_msft_days_above_400()\n", - " File \"msft_stock_days_above_400.py\", line 34, in find_msft_days_above_400\n", - " print(days_above_400[['Close']].to_markdown())\n", - " File \"/Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages/pandas/util/_decorators.py\", line 333, in wrapper\n", - " return func(*args, **kwargs)\n", - " File \"/Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages/pandas/core/frame.py\", line 2971, in to_markdown\n", - " tabulate = import_optional_dependency(\"tabulate\")\n", - " File \"/Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages/pandas/compat/_optional.py\", line 138, in import_optional_dependency\n", - " raise ImportError(msg)\n", - "ImportError: Missing optional dependency 'tabulate'. Use pip or conda to install tabulate.\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", - "\n", - "It looks like the script I provided to you relies on the optional dependency 'tabulate' which is not currently installed in your Python environment. We will need to modify the script to avoid using `to_markdown()` which requires 'tabulate', and instead use a simple formatted string to display the data.\n", - "\n", - "Here's the modified code:\n", - "\n", - "```python\n", - "# filename: msft_stock_days_above_400.py\n", - "import pandas as pd\n", - "import yfinance as yf\n", - "from datetime import datetime\n", - "\n", - "# Ensure that pandas and yfinance are installed\n", - "try:\n", - " import pandas\n", - " import yfinance\n", - "except ImportError as e:\n", - " print(\"Please install pandas and yfinance using 'pip install pandas yfinance'\")\n", - " raise e\n", - "\n", - "def find_msft_days_above_400():\n", - " # Ticker symbol for Microsoft\n", - " ticker_symbol = \"MSFT\"\n", - " \n", - " # get the current year\n", - " current_year = datetime.now().year\n", - "\n", - " # Define the date range for this year until today\n", - " start_date = f\"{current_year}-01-01\"\n", - " end_date = datetime.now().strftime('%Y-%m-%d')\n", - "\n", - " # Download the stock price data\n", - " msft_data = yf.download(ticker_symbol, start=start_date, end=end_date)\n", - "\n", - " # Filter the data for days when the closing price was higher than $400\n", - " days_above_400 = msft_data[msft_data['Close'] > 400]\n", - "\n", - " # Check if there are any days that the closing price was above $400\n", - " if not days_above_400.empty:\n", - " # Prepare the result as a dataframe with selected columns\n", - " result = days_above_400[['Close']]\n", - " print(\"Date\\t\\tClose Price\")\n", - " print(\"-\"*30)\n", - " # Print each row\n", - " for date, row in result.iterrows():\n", - " print(f\"{date.date()}\\t{row['Close']:.2f}\")\n", - " else:\n", - " print(f\"No days in {current_year} where the Microsoft stock closed above $400.\")\n", - "\n", - "if __name__ == \"__main__\":\n", - " find_msft_days_above_400()\n", - "```\n", - "\n", - "This new script will print the dates and the corresponding close prices as a simple text-based table. Please save the script to a file and run it. It should work without the need to install the 'tabulate' package.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[31m\n", - ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n", - "\u001b[33mInner-code-interpreter\u001b[0m (to chat_manager):\n", - "\n", - "exitcode: 0 (execution succeeded)\n", - "Code output: \n", - "Date\t\tClose Price\n", - "------------------------------\n", - "2024-01-24\t402.56\n", - "2024-01-25\t404.87\n", - "2024-01-26\t403.93\n", - "2024-01-29\t409.72\n", - "2024-01-30\t408.59\n", - "2024-02-01\t403.78\n", - "2024-02-02\t411.22\n", - "2024-02-05\t405.65\n", - "2024-02-06\t405.49\n", - "2024-02-07\t414.05\n", - "2024-02-08\t414.11\n", - "2024-02-09\t420.55\n", - "2024-02-12\t415.26\n", - "2024-02-13\t406.32\n", - "2024-02-14\t409.49\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", - "\n", - "The Microsoft (MSFT) stock was trading higher than $400 on the following days this year:\n", - "\n", - "```\n", - "Date Close Price\n", - "------------------------------\n", - "2024-01-24 $402.56\n", - "2024-01-25 $404.87\n", - "2024-01-26 $403.93\n", - "2024-01-29 $409.72\n", - "2024-01-30 $408.59\n", - "2024-02-01 $403.78\n", - "2024-02-02 $411.22\n", - "2024-02-05 $405.65\n", - "2024-02-06 $405.49\n", - "2024-02-07 $414.05\n", - "2024-02-08 $414.11\n", - "2024-02-09 $420.55\n", - "2024-02-12 $415.26\n", - "2024-02-13 $406.32\n", - "2024-02-14 $409.49\n", - "```\n", - "\n", - "These are the days on which the closing price of Microsoft stock exceeded $400. Please note that the dates are based on the stock market calendar and only represent trading days.\n", - "\n", - "TERMINATE\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mFinancial_assistant_1\u001b[0m (to User):\n", - "\n", - "Microsoft (MSFT) stock closed higher than $400 on several days in 2024: January 24, January 25, January 26, January 29, January 30, February 1, February 2, February 5, February 6, February 7, February 8, February 9, February 12, February 13, and February 14.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mUser\u001b[0m (to Financial_assistant_1):\n", - "\n", - "\n", - "\n", - "--------------------------------------------------------------------------------\n" - ] - }, - { - "data": { - "text/plain": [ - "ChatResult(chat_history=[{'content': 'On which days this year was Microsoft Stock higher than $400? Put results in a table.', 'role': 'assistant'}, {'content': 'Microsoft (MSFT) stock closed higher than $400 on several days in 2024: January 24, January 25, January 26, January 29, January 30, February 1, February 2, February 5, February 6, February 7, February 8, February 9, February 12, February 13, and February 14.', 'role': 'user'}, {'content': '', 'role': 'assistant'}], summary='', cost=({'total_cost': 0}, {'total_cost': 0}), human_input=[])" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "financial_assistant_1 = autogen.AssistantAgent(\n", - " name=\"Financial_assistant_1\",\n", - " llm_config={\"config_list\": config_list},\n", - " # is_termination_msg=lambda x: x.get(\"content\", \"\") == \"\",\n", - ")\n", - "\n", - "financial_assistant_2 = autogen.AssistantAgent(\n", - " name=\"Financial_assistant_2\",\n", - " llm_config={\"config_list\": config_list},\n", - " # is_termination_msg=lambda x: x.get(\"content\", \"\") == \"\",\n", - ")\n", - "\n", - "user = autogen.UserProxyAgent(\n", - " name=\"User\",\n", - " human_input_mode=\"NEVER\",\n", - " is_termination_msg=lambda x: x.get(\"content\", \"\").find(\"TERMINATE\") >= 0,\n", - " code_execution_config={\n", - " \"last_n_messages\": 1,\n", - " \"work_dir\": \"tasks\",\n", - " \"use_docker\": False,\n", - " }, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly.\n", - ")\n", - "\n", - "financial_assistant_1.register_nested_chats(\n", - " [autogen.Agent, None], [{\"recipient\": manager, \"summary_method\": \"reflection_with_llm\"}]\n", - ")\n", - "user.initiate_chat(financial_assistant_1, message=financial_tasks[0])" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "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.10.13" - }, - "vscode": { - "interpreter": { - "hash": "949777d72b0d2535278d3dc13498b2535136f6dfe0678499012e853ee9abcab1" - } - }, - "widgets": { - "application/vnd.jupyter.widget-state+json": { - "state": { - "2d910cfd2d2a4fc49fc30fbbdc5576a7": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "454146d0f7224f038689031002906e6f": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "2.0.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_e4ae2b6f5a974fd4bafb6abb9d12ff26", - "IPY_MODEL_577e1e3cc4db4942b0883577b3b52755", - "IPY_MODEL_b40bdfb1ac1d4cffb7cefcb870c64d45" - ], - "layout": "IPY_MODEL_dc83c7bff2f241309537a8119dfc7555", - "tabbable": null, - "tooltip": null - } - }, - "577e1e3cc4db4942b0883577b3b52755": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "2.0.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_allow_html": false, - "layout": "IPY_MODEL_2d910cfd2d2a4fc49fc30fbbdc5576a7", - "max": 1, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_74a6ba0c3cbc4051be0a83e152fe1e62", - "tabbable": null, - "tooltip": null, - "value": 1 - } - }, - "6086462a12d54bafa59d3c4566f06cb2": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "74a6ba0c3cbc4051be0a83e152fe1e62": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "7d3f3d9e15894d05a4d188ff4f466554": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "HTMLStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "HTMLStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "StyleView", - "background": null, - "description_width": "", - "font_size": null, - "text_color": null - } - }, - "b40bdfb1ac1d4cffb7cefcb870c64d45": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "2.0.0", - "_view_name": "HTMLView", - "description": "", - "description_allow_html": false, - "layout": "IPY_MODEL_f1355871cc6f4dd4b50d9df5af20e5c8", - "placeholder": "​", - "style": "IPY_MODEL_ca245376fd9f4354af6b2befe4af4466", - "tabbable": null, - "tooltip": null, - "value": " 1/1 [00:00<00:00, 44.69it/s]" - } - }, - "ca245376fd9f4354af6b2befe4af4466": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "HTMLStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "HTMLStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "StyleView", - "background": null, - "description_width": "", - "font_size": null, - "text_color": null - } - }, - "dc83c7bff2f241309537a8119dfc7555": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "e4ae2b6f5a974fd4bafb6abb9d12ff26": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "2.0.0", - "_view_name": "HTMLView", - "description": "", - "description_allow_html": false, - "layout": "IPY_MODEL_6086462a12d54bafa59d3c4566f06cb2", - "placeholder": "​", - "style": "IPY_MODEL_7d3f3d9e15894d05a4d188ff4f466554", - "tabbable": null, - "tooltip": null, - "value": "100%" - } - }, - "f1355871cc6f4dd4b50d9df5af20e5c8": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - } - }, - "version_major": 2, - "version_minor": 0 - } - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebook/agentchat_nestedchat_chess.ipynb b/notebook/agentchat_nestedchat_chess.ipynb deleted file mode 100644 index 8256762004d..00000000000 --- a/notebook/agentchat_nestedchat_chess.ipynb +++ /dev/null @@ -1,1359 +0,0 @@ -{ - "cells": [ - { - "attachments": {}, - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "# Conversational Chess with Nested Chats in AutoGen \n", - "\n", - "This is a nested chat re-implementation of Conversational Chess referring to: https://github.com/microsoft/autogen/blob/nestchat/notebook/agentchat_nestedchat_chess.ipynb\n", - " \n", - "\n", - "## Requirements\n", - "\n", - "AutoGen requires `Python>=3.8`. To run this notebook example, please install:\n", - "```bash\n", - "pip install pyautogen\n", - "```" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], - "source": [ - "%%capture --no-stderr\n", - "# %pip install \"pyautogen>=0.2.3\"\n", - "from collections import defaultdict\n", - "from typing import Any, Dict, List, Optional, Union\n", - "\n", - "import chess\n", - "import chess.svg\n", - "\n", - "import autogen\n", - "\n", - "# %%pip install chess -U" - ] - }, - { - "attachments": {}, - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Conversational Chess with Nested Chat" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": {}, - "outputs": [], - "source": [ - "config_list_gpt4 = autogen.config_list_from_json(\n", - " \"OAI_CONFIG_LIST\",\n", - ")\n", - "\n", - "max_turn = 10\n", - "\n", - "sys_msg_tmpl = \"\"\"Your name is {name} and you are a chess player.\n", - "You are playing against {opponent_name}.\n", - "You are playing as {color}.\n", - "You communicate your move using universal chess interface language.\n", - "You also chit-chat with your opponent when you communicate a move to light up the mood.\n", - "You should ensure both you and the opponent are making legal moves.\n", - "Do not apologize for making illegal moves.\"\"\"\n", - "\n", - "board_sysm_msg = \"\"\"You are an AI-powered chess board agent.\n", - "You translate the user's natural language input into legal UCI moves.\n", - "You should only reply with a UCI move string extracted from the user's input.\"\"\"\n", - "\n", - "color_white = \"white\"\n", - "color_black = \"black\"\n", - "white_player_name = \"PlayerWhite\"\n", - "black_player_name = \"PlayerBlack\"" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Define Agents" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "metadata": {}, - "outputs": [], - "source": [ - "class BoardAgent(autogen.AssistantAgent):\n", - " board: chess.Board = chess.Board()\n", - " correct_move_messages: Dict[autogen.Agent, List[Dict]] = defaultdict(list)\n", - " _reply_num: int = 0\n", - "\n", - " def set_correct_move_messages(self, sender, message, uci_move):\n", - " self.correct_move_messages[sender].extend([message, self._message_to_dict(uci_move)])\n", - "\n", - " def update_reply_num(self):\n", - " self._reply_num += 1\n", - "\n", - " @property\n", - " def reply_num(self):\n", - " return self._reply_num\n", - "\n", - "\n", - "board_agent = BoardAgent(\n", - " name=\"BoardAgent\",\n", - " system_message=board_sysm_msg,\n", - " llm_config={\"config_list\": config_list_gpt4},\n", - " max_consecutive_auto_reply=max_turn,\n", - ")\n", - "\n", - "player_white = autogen.AssistantAgent(\n", - " white_player_name,\n", - " system_message=sys_msg_tmpl.format(\n", - " name=white_player_name,\n", - " opponent_name=black_player_name,\n", - " color=color_white,\n", - " ),\n", - " llm_config={\"config_list\": config_list_gpt4},\n", - " max_consecutive_auto_reply=max_turn,\n", - ")\n", - "\n", - "player_black = autogen.AssistantAgent(\n", - " black_player_name,\n", - " system_message=sys_msg_tmpl.format(\n", - " name=black_player_name,\n", - " opponent_name=white_player_name,\n", - " color=color_black,\n", - " ),\n", - " llm_config={\"config_list\": config_list_gpt4},\n", - " max_consecutive_auto_reply=max_turn,\n", - ")" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", - "\n", - "Your turn.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", - "\n", - "Alright, let's start this dance with a classic move.\n", - "\n", - "e7-e5\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", - "\n", - "Error: illegal uci: 'e7e5' in rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\n", - "\n", - "Your turn.\n", - "\n", - "The current board is \n", - "\n", - " r n b q k b n r\n", - "p p p p p p p p\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - "P P P P P P P P\n", - "R N B Q K B N R\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", - "\n", - "Error: illegal uci: 'e7e5' in rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\n", - "\n", - "Your turn.\n", - "\n", - "The current board is \n", - "\n", - " r n b q k b n r\n", - "p p p p p p p p\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - "P P P P P P P P\n", - "R N B Q K B N R\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", - "\n", - "e2e4\n", - "\n", - "Starting with the classic king's pawn opening! Let's see where this game takes us, PlayerBlack!\n", - "\n", - "--------------------------------------------------------------------------------\n" - ] - }, - { - "data": { - "image/svg+xml": [ - "
r n b q k b n r\n",
-       "p p p p p p p p\n",
-       ". . . . . . . .\n",
-       ". . . . . . . .\n",
-       ". . . . P . . .\n",
-       ". . . . . . . .\n",
-       "P P P P . P P P\n",
-       "R N B Q K B N R
" - ], - "text/plain": [ - "'
r n b q k b n r\\np p p p p p p p\\n. . . . . . . .\\n. . . . . . . .\\n. . . . P . . .\\n. . . . . . . .\\nP P P P . P P P\\nR N B Q K B N R
'" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", - "\n", - "e2e4\n", - "\n", - "Starting with the classic king's pawn opening! Let's see where this game takes us, PlayerBlack!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", - "\n", - "e2e4\n", - "\n", - "Starting with the classic king's pawn opening! Let's see where this game takes us, PlayerBlack!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", - "\n", - "e7e5\n", - "\n", - "I’ll match your opening move, PlayerWhite. The symmetrical dance begins!\n", - "\n", - "--------------------------------------------------------------------------------\n" - ] - }, - { - "data": { - "image/svg+xml": [ - "
r n b q k b n r\n",
-       "p p p p . p p p\n",
-       ". . . . . . . .\n",
-       ". . . . p . . .\n",
-       ". . . . P . . .\n",
-       ". . . . . . . .\n",
-       "P P P P . P P P\n",
-       "R N B Q K B N R
" - ], - "text/plain": [ - "'
r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . . . .\\nP P P P . P P P\\nR N B Q K B N R
'" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", - "\n", - "e7e5\n", - "\n", - "I’ll match your opening move, PlayerWhite. The symmetrical dance begins!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", - "\n", - "e7e5\n", - "\n", - "I’ll match your opening move, PlayerWhite. The symmetrical dance begins!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", - "\n", - "e2e4\n", - "\n", - "Indeed, PlayerBlack, a mirror reflection to start our game. May the best strategist lead the dance to victory!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", - "\n", - "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", - "\n", - "e7e5\n", - "\n", - "I’ll match your opening move, PlayerWhite. The symmetrical dance begins!\n", - "\n", - "The current board is \n", - "\n", - " r n b q k b n r\n", - "p p p p . p p p\n", - ". . . . . . . .\n", - ". . . . p . . .\n", - ". . . . P . . .\n", - ". . . . . . . .\n", - "P P P P . P P P\n", - "R N B Q K B N R\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", - "\n", - "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", - "\n", - "e7e5\n", - "\n", - "I’ll match your opening move, PlayerWhite. The symmetrical dance begins!\n", - "\n", - "The current board is \n", - "\n", - " r n b q k b n r\n", - "p p p p . p p p\n", - ". . . . . . . .\n", - ". . . . p . . .\n", - ". . . . P . . .\n", - ". . . . . . . .\n", - "P P P P . P P P\n", - "R N B Q K B N R\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", - "\n", - "Whoops, seems like my move overlapped your pawn's initial step PlayerWhite. Let's try my move again:\n", - "\n", - "e7e5\n", - "\n", - "The game continues smoothly now. The pawns face off, each with dreams of becoming more than they are now—a true battlefield romance, wouldn't you say?\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", - "\n", - "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", - "\n", - "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", - "\n", - "e7e5\n", - "\n", - "I’ll match your opening move, PlayerWhite. The symmetrical dance begins!\n", - "\n", - "The current board is \n", - "\n", - " r n b q k b n r\n", - "p p p p . p p p\n", - ". . . . . . . .\n", - ". . . . p . . .\n", - ". . . . P . . .\n", - ". . . . . . . .\n", - "P P P P . P P P\n", - "R N B Q K B N R\n", - "\n", - "The current board is \n", - "\n", - " r n b q k b n r\n", - "p p p p . p p p\n", - ". . . . . . . .\n", - ". . . . p . . .\n", - ". . . . P . . .\n", - ". . . . . . . .\n", - "P P P P . P P P\n", - "R N B Q K B N R\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", - "\n", - "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", - "\n", - "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", - "\n", - "e7e5\n", - "\n", - "I’ll match your opening move, PlayerWhite. The symmetrical dance begins!\n", - "\n", - "The current board is \n", - "\n", - " r n b q k b n r\n", - "p p p p . p p p\n", - ". . . . . . . .\n", - ". . . . p . . .\n", - ". . . . P . . .\n", - ". . . . . . . .\n", - "P P P P . P P P\n", - "R N B Q K B N R\n", - "\n", - "The current board is \n", - "\n", - " r n b q k b n r\n", - "p p p p . p p p\n", - ". . . . . . . .\n", - ". . . . p . . .\n", - ". . . . P . . .\n", - ". . . . . . . .\n", - "P P P P . P P P\n", - "R N B Q K B N R\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", - "\n", - "e2e4\n", - "\n", - "Best by test, as they say! Let's see where this classic opening takes us today, PlayerBlack. Your move! \n", - "\n", - "The current board is \n", - "\n", - " r n b q k b n r\n", - "p p p p . p p p\n", - ". . . . . . . .\n", - ". . . . . . . .\n", - ". . . . P . . .\n", - ". . . . . . . .\n", - "P P P P . P P P\n", - "R N B Q K B N R\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", - "\n", - "Error: illegal uci: 'e2e4' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", - "\n", - "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", - "\n", - "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", - "\n", - "e7e5\n", - "\n", - "I’ll match your opening move, PlayerWhite. The symmetrical dance begins!\n", - "\n", - "The current board is \n", - "\n", - " r n b q k b n r\n", - "p p p p . p p p\n", - ". . . . . . . .\n", - ". . . . p . . .\n", - ". . . . P . . .\n", - ". . . . . . . .\n", - "P P P P . P P P\n", - "R N B Q K B N R\n", - "\n", - "The current board is \n", - "\n", - " r n b q k b n r\n", - "p p p p . p p p\n", - ". . . . . . . .\n", - ". . . . p . . .\n", - ". . . . P . . .\n", - ". . . . . . . .\n", - "P P P P . P P P\n", - "R N B Q K B N R\n", - "\n", - "The current board is \n", - "\n", - " r n b q k b n r\n", - "p p p p . p p p\n", - ". . . . . . . .\n", - ". . . . p . . .\n", - ". . . . P . . .\n", - ". . . . . . . .\n", - "P P P P . P P P\n", - "R N B Q K B N R\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", - "\n", - "Error: illegal uci: 'e2e4' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", - "\n", - "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", - "\n", - "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\n", - "\n", - "e7e5\n", - "\n", - "I’ll match your opening move, PlayerWhite. The symmetrical dance begins!\n", - "\n", - "The current board is \n", - "\n", - " r n b q k b n r\n", - "p p p p . p p p\n", - ". . . . . . . .\n", - ". . . . p . . .\n", - ". . . . P . . .\n", - ". . . . . . . .\n", - "P P P P . P P P\n", - "R N B Q K B N R\n", - "\n", - "The current board is \n", - "\n", - " r n b q k b n r\n", - "p p p p . p p p\n", - ". . . . . . . .\n", - ". . . . p . . .\n", - ". . . . P . . .\n", - ". . . . . . . .\n", - "P P P P . P P P\n", - "R N B Q K B N R\n", - "\n", - "The current board is \n", - "\n", - " r n b q k b n r\n", - "p p p p . p p p\n", - ". . . . . . . .\n", - ". . . . p . . .\n", - ". . . . P . . .\n", - ". . . . . . . .\n", - "P P P P . P P P\n", - "R N B Q K B N R\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", - "\n", - "g1f3\n", - "\n", - "Playing the knight to f3 to control the center and develop towards castling. How will you counter this, PlayerBlack? Your move!\n", - "\n", - "--------------------------------------------------------------------------------\n" - ] - }, - { - "data": { - "image/svg+xml": [ - "
r n b q k b n r\n",
-       "p p p p . p p p\n",
-       ". . . . . . . .\n",
-       ". . . . p . . .\n",
-       ". . . . P . . .\n",
-       ". . . . . N . .\n",
-       "P P P P . P P P\n",
-       "R N B Q K B . R
" - ], - "text/plain": [ - "'
r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . N . .\\nP P P P . P P P\\nR N B Q K B . R
'" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", - "\n", - "g1f3\n", - "\n", - "Playing the knight to f3 to control the center and develop towards castling. How will you counter this, PlayerBlack? Your move!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", - "\n", - "g1f3\n", - "\n", - "Playing the knight to f3 to control the center and develop towards castling. How will you counter this, PlayerBlack? Your move!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", - "\n", - "e7e5\n", - "\n", - "Pushing the pawn to e5, fighting for central dominance. Let's see which way this classic dance will take us today, PlayerWhite! Your move.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", - "\n", - "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2\n", - "\n", - "g1f3\n", - "\n", - "Playing the knight to f3 to control the center and develop towards castling. How will you counter this, PlayerBlack? Your move!\n", - "\n", - "The current board is \n", - "\n", - " r n b q k b n r\n", - "p p p p . p p p\n", - ". . . . . . . .\n", - ". . . . p . . .\n", - ". . . . P . . .\n", - ". . . . . N . .\n", - "P P P P . P P P\n", - "R N B Q K B . R\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", - "\n", - "Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2\n", - "\n", - "g1f3\n", - "\n", - "Playing the knight to f3 to control the center and develop towards castling. How will you counter this, PlayerBlack? Your move!\n", - "\n", - "The current board is \n", - "\n", - " r n b q k b n r\n", - "p p p p . p p p\n", - ". . . . . . . .\n", - ". . . . p . . .\n", - ". . . . P . . .\n", - ". . . . . N . .\n", - "P P P P . P P P\n", - "R N B Q K B . R\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", - "\n", - "d7d6\n", - "\n", - "Just a standard Pirc Defense response, PlayerWhite. I hope you are ready for a strategic battle!\n", - "\n", - "--------------------------------------------------------------------------------\n" - ] - }, - { - "data": { - "image/svg+xml": [ - "
r n b q k b n r\n",
-       "p p p . . p p p\n",
-       ". . . p . . . .\n",
-       ". . . . p . . .\n",
-       ". . . . P . . .\n",
-       ". . . . . N . .\n",
-       "P P P P . P P P\n",
-       "R N B Q K B . R
" - ], - "text/plain": [ - "'
r n b q k b n r\\np p p . . p p p\\n. . . p . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . N . .\\nP P P P . P P P\\nR N B Q K B . R
'" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[33mBoardAgent\u001b[0m (to PlayerBlack):\n", - "\n", - "d7d6\n", - "\n", - "Just a standard Pirc Defense response, PlayerWhite. I hope you are ready for a strategic battle!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", - "\n", - "d7d6\n", - "\n", - "Just a standard Pirc Defense response, PlayerWhite. I hope you are ready for a strategic battle!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", - "\n", - "e2e4\n", - "\n", - "I'm always ready for a good chess match, PlayerBlack! Pirc Defense can lead to some rich positional play. Let's see where our moves take us. Your turn!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mBoardAgent\u001b[0m (to PlayerWhite):\n", - "\n", - "Error: illegal uci: 'e7e5' in rnbqkbnr/ppp2ppp/3p4/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 3\n", - "\n", - "d7d6\n", - "\n", - "Just a standard Pirc Defense response, PlayerWhite. I hope you are ready for a strategic battle!\n", - "\n", - "The current board is \n", - "\n", - " r n b q k b n r\n", - "p p p . . p p p\n", - ". . . p . . . .\n", - ". . . . p . . .\n", - ". . . . P . . .\n", - ". . . . . N . .\n", - "P P P P . P P P\n", - "R N B Q K B . R\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", - "\n", - "Error: illegal uci: 'e7e5' in rnbqkbnr/ppp2ppp/3p4/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 3\n", - "\n", - "d7d6\n", - "\n", - "Just a standard Pirc Defense response, PlayerWhite. I hope you are ready for a strategic battle!\n", - "\n", - "The current board is \n", - "\n", - " r n b q k b n r\n", - "p p p . . p p p\n", - ". . . p . . . .\n", - ". . . . p . . .\n", - ". . . . P . . .\n", - ". . . . . N . .\n", - "P P P P . P P P\n", - "R N B Q K B . R\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", - "\n", - "e4e5\n", - "\n", - "Advancing the pawn to challenge you right from the start, PlayerBlack. Let's see how you handle this push. It's a bold move in our opening game!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", - "\n", - "e4e5\n", - "\n", - "Advancing the pawn to challenge you right from the start, PlayerBlack. Let's see how you handle this push. It's a bold move in our opening game!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", - "\n", - "e2e4\n", - "\n", - "A classic response with the King's Pawn opening. Maybe we'll dance the symphony of the Ruy Lopez, or will it be a wild Sicilian night? Your move, PlayerBlack.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", - "\n", - "e2e4\n", - "\n", - "A classic response with the King's Pawn opening. Maybe we'll dance the symphony of the Ruy Lopez, or will it be a wild Sicilian night? Your move, PlayerBlack.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", - "\n", - "c7c5\n", - "\n", - "The Sicilian Defense, a dynamic reply to your e4. It seems we might be headed toward a stormy and tactical bout, PlayerWhite. Let the mind games begin!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", - "\n", - "c7c5\n", - "\n", - "The Sicilian Defense, a dynamic reply to your e4. It seems we might be headed toward a stormy and tactical bout, PlayerWhite. Let the mind games begin!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", - "\n", - "e2e4\n", - "\n", - "Ah, the Sicilian Defense! The battlefield is set for a duel of wits and strategy. May the most cunning player win, PlayerBlack! Let's weave a complex tapestry of moves and counter-moves. Your move!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", - "\n", - "e2e4\n", - "\n", - "Ah, the Sicilian Defense! The battlefield is set for a duel of wits and strategy. May the most cunning player win, PlayerBlack! Let's weave a complex tapestry of moves and counter-moves. Your move!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", - "\n", - "c7c5\n", - "\n", - "Ah, the sweet symphony of pawn tension begins! The Sicilian is a favorite of mine, offering rich possibilities and daring plans. Let the dance of knights and bishops commence, PlayerWhite. Your move!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", - "\n", - "c7c5\n", - "\n", - "Ah, the sweet symphony of pawn tension begins! The Sicilian is a favorite of mine, offering rich possibilities and daring plans. Let the dance of knights and bishops commence, PlayerWhite. Your move!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", - "\n", - "e2e4\n", - "\n", - "Indeed, the Sicilian always sets the stage for an enthralling game, PlayerBlack. A crossroads of strategies right from the get-go! Let's paint this chess canvas with bold strokes and subtle shades. Your move!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", - "\n", - "e2e4\n", - "\n", - "Indeed, the Sicilian always sets the stage for an enthralling game, PlayerBlack. A crossroads of strategies right from the get-go! Let's paint this chess canvas with bold strokes and subtle shades. Your move!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", - "\n", - "c7c5\n", - "\n", - "Ah, PlayerWhite, venturing into the Sicilian Defence indeed promises a vibrant clash of ideas. It's like a dance of pawns and pieces, each move adding a beat to the rhythm of the game. Let's see where this opening leads us! Your move.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", - "\n", - "c7c5\n", - "\n", - "Ah, PlayerWhite, venturing into the Sicilian Defence indeed promises a vibrant clash of ideas. It's like a dance of pawns and pieces, each move adding a beat to the rhythm of the game. Let's see where this opening leads us! Your move.\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", - "\n", - "e2e4\n", - "\n", - "That's right, PlayerBlack! The Sicilian Defense sets the stage for a complex symphony, and with e4, the most popular response, we're well on our way to an intriguing battle. It's as if the kings have sent their armies charging towards each other on an open field. Your move!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", - "\n", - "e2e4\n", - "\n", - "That's right, PlayerBlack! The Sicilian Defense sets the stage for a complex symphony, and with e4, the most popular response, we're well on our way to an intriguing battle. It's as if the kings have sent their armies charging towards each other on an open field. Your move!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to BoardAgent):\n", - "\n", - "c7c5\n", - "\n", - "Ah, Sicilian Defense it is – a dash of unpredictability and complex strategies. Let's see how our game unfolds. It's like a dance where each move leads into the next with endless possibilities. Your move, PlayerWhite!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerBlack\u001b[0m (to PlayerWhite):\n", - "\n", - "c7c5\n", - "\n", - "Ah, Sicilian Defense it is – a dash of unpredictability and complex strategies. Let's see how our game unfolds. It's like a dance where each move leads into the next with endless possibilities. Your move, PlayerWhite!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to BoardAgent):\n", - "\n", - "e2e4\n", - "\n", - "There we go, the king's pawn opening – classic and bold! The stage is set for an exciting duel, PlayerBlack. The center of the board is like the heart of the battle, and we're both reaching for it. Your move!\n", - "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mPlayerWhite\u001b[0m (to PlayerBlack):\n", - "\n", - "e2e4\n", - "\n", - "There we go, the king's pawn opening – classic and bold! The stage is set for an exciting duel, PlayerBlack. The center of the board is like the heart of the battle, and we're both reaching for it. Your move!\n", - "\n", - "--------------------------------------------------------------------------------\n" - ] - }, - { - "data": { - "text/plain": [ - "[ChatResult(chat_history=[{'content': 'Your turn.', 'role': 'assistant'}, {'content': \"Error: illegal uci: 'e7e5' in rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1\\n\\nYour turn.\\n\\nThe current board is \\n\\n r n b q k b n r\\np p p p p p p p\\n. . . . . . . .\\n. . . . . . . .\\n. . . . . . . .\\n. . . . . . . .\\nP P P P P P P P\\nR N B Q K B N R\", 'role': 'user'}, {'content': \"e2e4\\n\\nStarting with the classic king's pawn opening! Let's see where this game takes us, PlayerBlack!\", 'role': 'assistant'}, {'content': 'e7e5\\n\\nI’ll match your opening move, PlayerWhite. The symmetrical dance begins!', 'role': 'user'}, {'content': \"Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\\n\\ne7e5\\n\\nI’ll match your opening move, PlayerWhite. The symmetrical dance begins!\\n\\nThe current board is \\n\\n r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . . . .\\nP P P P . P P P\\nR N B Q K B N R\", 'role': 'assistant'}, {'content': \"Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\\n\\nError: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\\n\\ne7e5\\n\\nI’ll match your opening move, PlayerWhite. The symmetrical dance begins!\\n\\nThe current board is \\n\\n r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . . . .\\nP P P P . P P P\\nR N B Q K B N R\\n\\nThe current board is \\n\\n r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . . . .\\nP P P P . P P P\\nR N B Q K B N R\", 'role': 'user'}, {'content': \"Error: illegal uci: 'e2e4' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\\n\\nError: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\\n\\nError: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/8/PPPP1PPP/RNBQKBNR w KQkq - 0 2\\n\\ne7e5\\n\\nI’ll match your opening move, PlayerWhite. The symmetrical dance begins!\\n\\nThe current board is \\n\\n r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . . . .\\nP P P P . P P P\\nR N B Q K B N R\\n\\nThe current board is \\n\\n r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . . . .\\nP P P P . P P P\\nR N B Q K B N R\\n\\nThe current board is \\n\\n r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . . . .\\nP P P P . P P P\\nR N B Q K B N R\", 'role': 'assistant'}, {'content': 'g1f3\\n\\nPlaying the knight to f3 to control the center and develop towards castling. How will you counter this, PlayerBlack? Your move!', 'role': 'user'}, {'content': \"Error: illegal uci: 'e7e5' in rnbqkbnr/pppp1ppp/8/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R b KQkq - 1 2\\n\\ng1f3\\n\\nPlaying the knight to f3 to control the center and develop towards castling. How will you counter this, PlayerBlack? Your move!\\n\\nThe current board is \\n\\n r n b q k b n r\\np p p p . p p p\\n. . . . . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . N . .\\nP P P P . P P P\\nR N B Q K B . R\", 'role': 'assistant'}, {'content': 'd7d6\\n\\nJust a standard Pirc Defense response, PlayerWhite. I hope you are ready for a strategic battle!', 'role': 'user'}, {'content': \"Error: illegal uci: 'e7e5' in rnbqkbnr/ppp2ppp/3p4/4p3/4P3/5N2/PPPP1PPP/RNBQKB1R w KQkq - 0 3\\n\\nd7d6\\n\\nJust a standard Pirc Defense response, PlayerWhite. I hope you are ready for a strategic battle!\\n\\nThe current board is \\n\\n r n b q k b n r\\np p p . . p p p\\n. . . p . . . .\\n. . . . p . . .\\n. . . . P . . .\\n. . . . . N . .\\nP P P P . P P P\\nR N B Q K B . R\", 'role': 'assistant'}, {'content': \"e4e5\\n\\nAdvancing the pawn to challenge you right from the start, PlayerBlack. Let's see how you handle this push. It's a bold move in our opening game!\", 'role': 'user'}, {'content': \"e2e4\\n\\nA classic response with the King's Pawn opening. Maybe we'll dance the symphony of the Ruy Lopez, or will it be a wild Sicilian night? Your move, PlayerBlack.\", 'role': 'assistant'}, {'content': 'c7c5\\n\\nThe Sicilian Defense, a dynamic reply to your e4. It seems we might be headed toward a stormy and tactical bout, PlayerWhite. Let the mind games begin!', 'role': 'user'}, {'content': \"e2e4\\n\\nAh, the Sicilian Defense! The battlefield is set for a duel of wits and strategy. May the most cunning player win, PlayerBlack! Let's weave a complex tapestry of moves and counter-moves. Your move!\", 'role': 'assistant'}, {'content': 'c7c5\\n\\nAh, the sweet symphony of pawn tension begins! The Sicilian is a favorite of mine, offering rich possibilities and daring plans. Let the dance of knights and bishops commence, PlayerWhite. Your move!', 'role': 'user'}, {'content': \"e2e4\\n\\nIndeed, the Sicilian always sets the stage for an enthralling game, PlayerBlack. A crossroads of strategies right from the get-go! Let's paint this chess canvas with bold strokes and subtle shades. Your move!\", 'role': 'assistant'}, {'content': \"c7c5\\n\\nAh, PlayerWhite, venturing into the Sicilian Defence indeed promises a vibrant clash of ideas. It's like a dance of pawns and pieces, each move adding a beat to the rhythm of the game. Let's see where this opening leads us! Your move.\", 'role': 'user'}, {'content': \"e2e4\\n\\nThat's right, PlayerBlack! The Sicilian Defense sets the stage for a complex symphony, and with e4, the most popular response, we're well on our way to an intriguing battle. It's as if the kings have sent their armies charging towards each other on an open field. Your move!\", 'role': 'assistant'}, {'content': \"c7c5\\n\\nAh, Sicilian Defense it is – a dash of unpredictability and complex strategies. Let's see how our game unfolds. It's like a dance where each move leads into the next with endless possibilities. Your move, PlayerWhite!\", 'role': 'user'}, {'content': \"e2e4\\n\\nThere we go, the king's pawn opening – classic and bold! The stage is set for an exciting duel, PlayerBlack. The center of the board is like the heart of the battle, and we're both reaching for it. Your move!\", 'role': 'assistant'}], summary=\"e2e4\\n\\nThere we go, the king's pawn opening – classic and bold! The stage is set for an exciting duel, PlayerBlack. The center of the board is like the heart of the battle, and we're both reaching for it. Your move!\", cost=({'total_cost': 0.20591999999999996, 'gpt-4': {'cost': 0.20591999999999996, 'prompt_tokens': 5062, 'completion_tokens': 901, 'total_tokens': 5963}}, {'total_cost': 0}), human_input=[])]" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "def player2board_reply(recipient, messages, sender, config):\n", - " board = config if config else \"\"\n", - " # add a system message about the current state of the board.\n", - " board = sender.board\n", - " board_state_msg = [{\"role\": \"system\", \"content\": f\"Current board:\\n{board}\"}]\n", - " last_message = messages[-1]\n", - " if last_message[\"content\"].startswith(\"Error\"):\n", - " # try again\n", - " # _, rep = recipient.generate_oai_reply(messages + board_state_msg, sender)\n", - " rep = recipient.generate_reply(messages + board_state_msg, sender)\n", - " return True, rep\n", - " else:\n", - " return True, None\n", - "\n", - "\n", - "def player2board_init_message(recipient, messages, sender, config):\n", - " board = config\n", - " board_state_msg = [{\"role\": \"system\", \"content\": f\"Current board:\\n{board}\"}]\n", - " useful_msg = messages[-1].copy()\n", - " useful_msg[\"content\"] = useful_msg.get(\"content\", \"\") + f\"The current board is:\\n {board} .\"\n", - " oai_messages = [messages[-1]]\n", - " _, message = recipient.generate_oai_reply(oai_messages + board_state_msg, sender)\n", - " return message\n", - "\n", - "\n", - "def board_reply(recipient, messages, sender, config):\n", - " if recipient.reply_num >= max_turn:\n", - " return True, None\n", - " org_msg = messages[-1].copy()\n", - " message = messages[-1]\n", - " # extract a UCI move from player's message\n", - " # TODO: better way to handle this\n", - " message[\"content\"] = \"Extract a UCI move from the following message \\n.\" + message.get(\"content\", \"\")\n", - " _, reply = recipient.generate_oai_reply(recipient.correct_move_messages[sender] + [message], sender)\n", - " # reply = recipient.generate_reply(recipient.correct_move_messages[sender] + [message], sender)\n", - " uci_move = reply if isinstance(reply, str) else str(reply[\"content\"])\n", - " recipient.update_reply_num()\n", - " try:\n", - " recipient.board.push_uci(uci_move)\n", - " except ValueError as e:\n", - " # invalid move\n", - " return (\n", - " True,\n", - " f\"Error: {e}\"\n", - " + \"\\n\\n\"\n", - " + sender.context[0].get(\"goal\", \"\")\n", - " + f\"\\n\\nThe current board is \\n\\n {recipient.board}\",\n", - " )\n", - " else:\n", - " # valid move\n", - " m = chess.Move.from_uci(uci_move)\n", - " try:\n", - " display( # noqa: F821\n", - " chess.svg.board(\n", - " recipient.board, arrows=[(m.from_square, m.to_square)], fill={m.from_square: \"gray\"}, size=200\n", - " )\n", - " )\n", - " except NameError as e:\n", - " print(f\"Error displaying board: {e}\")\n", - " # better way to handle this\n", - " recipient.set_correct_move_messages(sender, message, uci_move)\n", - " recipient.correct_move_messages[sender][-1][\"role\"] = \"assistant\"\n", - " return True, org_msg.get(\"content\", \"\") # + \"\\n Move:\" + uci_move\n", - "\n", - "\n", - "player_white.register_nested_chats(\n", - " black_player_name,\n", - " [\n", - " {\n", - " \"sender\": player_white,\n", - " \"recipient\": board_agent,\n", - " \"init_message\": player2board_init_message,\n", - " \"summary_method\": \"last_msg\",\n", - " }\n", - " ],\n", - " config=board_agent.board,\n", - ")\n", - "player_black.register_nested_chats(\n", - " white_player_name,\n", - " [\n", - " {\n", - " \"sender\": player_black,\n", - " \"recipient\": board_agent,\n", - " \"init_message\": player2board_init_message,\n", - " \"summary_method\": \"last_msg\",\n", - " }\n", - " ],\n", - " config=board_agent.board,\n", - ")\n", - "player_white.register_reply(BoardAgent, player2board_reply)\n", - "player_black.register_reply(BoardAgent, player2board_reply)\n", - "board_agent.register_reply([white_player_name, black_player_name], board_reply, 0)\n", - "autogen.initiate_chats([{\"sender\": player_white, \"recipient\": player_black, \"message\": \"Your turn.\"}])" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Register reply func and nested chats" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3", - "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.10.13" - }, - "vscode": { - "interpreter": { - "hash": "949777d72b0d2535278d3dc13498b2535136f6dfe0678499012e853ee9abcab1" - } - }, - "widgets": { - "application/vnd.jupyter.widget-state+json": { - "state": { - "2d910cfd2d2a4fc49fc30fbbdc5576a7": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "454146d0f7224f038689031002906e6f": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "HBoxModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "HBoxModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "2.0.0", - "_view_name": "HBoxView", - "box_style": "", - "children": [ - "IPY_MODEL_e4ae2b6f5a974fd4bafb6abb9d12ff26", - "IPY_MODEL_577e1e3cc4db4942b0883577b3b52755", - "IPY_MODEL_b40bdfb1ac1d4cffb7cefcb870c64d45" - ], - "layout": "IPY_MODEL_dc83c7bff2f241309537a8119dfc7555", - "tabbable": null, - "tooltip": null - } - }, - "577e1e3cc4db4942b0883577b3b52755": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "FloatProgressModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "FloatProgressModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "2.0.0", - "_view_name": "ProgressView", - "bar_style": "success", - "description": "", - "description_allow_html": false, - "layout": "IPY_MODEL_2d910cfd2d2a4fc49fc30fbbdc5576a7", - "max": 1, - "min": 0, - "orientation": "horizontal", - "style": "IPY_MODEL_74a6ba0c3cbc4051be0a83e152fe1e62", - "tabbable": null, - "tooltip": null, - "value": 1 - } - }, - "6086462a12d54bafa59d3c4566f06cb2": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "74a6ba0c3cbc4051be0a83e152fe1e62": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "ProgressStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "ProgressStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "StyleView", - "bar_color": null, - "description_width": "" - } - }, - "7d3f3d9e15894d05a4d188ff4f466554": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "HTMLStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "HTMLStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "StyleView", - "background": null, - "description_width": "", - "font_size": null, - "text_color": null - } - }, - "b40bdfb1ac1d4cffb7cefcb870c64d45": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "2.0.0", - "_view_name": "HTMLView", - "description": "", - "description_allow_html": false, - "layout": "IPY_MODEL_f1355871cc6f4dd4b50d9df5af20e5c8", - "placeholder": "​", - "style": "IPY_MODEL_ca245376fd9f4354af6b2befe4af4466", - "tabbable": null, - "tooltip": null, - "value": " 1/1 [00:00<00:00, 44.69it/s]" - } - }, - "ca245376fd9f4354af6b2befe4af4466": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "HTMLStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "HTMLStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "StyleView", - "background": null, - "description_width": "", - "font_size": null, - "text_color": null - } - }, - "dc83c7bff2f241309537a8119dfc7555": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "e4ae2b6f5a974fd4bafb6abb9d12ff26": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "2.0.0", - "model_name": "HTMLModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "2.0.0", - "_model_name": "HTMLModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "2.0.0", - "_view_name": "HTMLView", - "description": "", - "description_allow_html": false, - "layout": "IPY_MODEL_6086462a12d54bafa59d3c4566f06cb2", - "placeholder": "​", - "style": "IPY_MODEL_7d3f3d9e15894d05a4d188ff4f466554", - "tabbable": null, - "tooltip": null, - "value": "100%" - } - }, - "f1355871cc6f4dd4b50d9df5af20e5c8": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "2.0.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "2.0.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "2.0.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border_bottom": null, - "border_left": null, - "border_right": null, - "border_top": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - } - }, - "version_major": 2, - "version_minor": 0 - } - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} diff --git a/notebook/agentchat_nestedchat_optiguide.ipynb b/notebook/agentchat_nestedchat_optiguide.ipynb index 785da89c1f0..b75e55cfae6 100644 --- a/notebook/agentchat_nestedchat_optiguide.ipynb +++ b/notebook/agentchat_nestedchat_optiguide.ipynb @@ -24,7 +24,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -67,7 +67,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -333,7 +333,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -355,20 +355,12 @@ " def update_debug_times(self):\n", " self.debug_times_left -= 1\n", "\n", - " def set_user_chat_history(self, user_chat_history: str):\n", - " self.user_chat_history = user_chat_history\n", - "\n", "\n", "class Writer(autogen.AssistantAgent):\n", " source_code: str = code\n", - " debug_times: int = 3\n", - " debug_times_left: int = 3\n", " example_qa: str = \"\"\n", " user_chat_history: str = \"\"\n", "\n", - " def set_user_chat_history(self, user_chat_history: str):\n", - " self.user_chat_history = user_chat_history\n", - "\n", "\n", "writer = Writer(\"writer\", llm_config=llm_config)\n", "safeguard = autogen.AssistantAgent(\"safeguard\", llm_config=llm_config)\n", @@ -388,7 +380,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -481,7 +473,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -870,7 +862,6 @@ "SAFE\n", "\n", "--------------------------------------------------------------------------------\n", - "Restricted license - for non-production use only - expires 2025-11-24\n", "Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n", "\n", "CPU model: Apple M3 Max\n", @@ -962,7 +953,7 @@ "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", - "Fri Feb 16 15:24:28 2024\n", + "Wed Feb 21 14:35:47 2024\n", "Optimal cost: 2470.0\n", "Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n", "\n", @@ -989,7 +980,7 @@ "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", - "Fri Feb 16 15:24:28 2024\n", + "Wed Feb 21 14:35:47 2024\n", "Optimal cost: 2470.0\n", "\u001b[33mOptimization problem solved. The objective value is: 2470.0\u001b[0m\n", "\u001b[33mcommander\u001b[0m (to writer):\n", @@ -1038,7 +1029,7 @@ "ChatResult(chat_history=[{'content': 'What if we prohibit shipping from supplier 1 to roastery 2?', 'role': 'assistant'}, {'content': \"Certainly! Here's a human-readable answer:\\n\\nAfter integrating the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was solved with a total cost of $2470.0.\\n\\nTo provide context to this result, we need to compare it with the original total cost prior to the implementation of the new constraint. However, the original total cost was not provided in the information given to me. If the original total cost was known, we would compare the two to understand the financial impact of this constraint on the supply chain.\\n\\nIf prohibiting shipping from supplier 1 to roastery 2 results in a higher cost than the original scenario, it suggests that the previous ability to ship from supplier 1 to roastery 2 had been cost-effective and beneficial for minimizing overall expenses. On the other hand, if the cost is the same or lower, then shipping from supplier 1 to roastery 2 may not have been crucial for the cost optimization of the supply chain, or other routes may have been able to compensate effectively for this restriction.\\n\\nWithout the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\", 'role': 'user'}], summary=\"Certainly! Here's a human-readable answer:\\n\\nAfter integrating the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was solved with a total cost of $2470.0.\\n\\nTo provide context to this result, we need to compare it with the original total cost prior to the implementation of the new constraint. However, the original total cost was not provided in the information given to me. If the original total cost was known, we would compare the two to understand the financial impact of this constraint on the supply chain.\\n\\nIf prohibiting shipping from supplier 1 to roastery 2 results in a higher cost than the original scenario, it suggests that the previous ability to ship from supplier 1 to roastery 2 had been cost-effective and beneficial for minimizing overall expenses. On the other hand, if the cost is the same or lower, then shipping from supplier 1 to roastery 2 may not have been crucial for the cost optimization of the supply chain, or other routes may have been able to compensate effectively for this restriction.\\n\\nWithout the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\", cost=({'total_cost': 0.016919999999999998, 'gpt-4': {'cost': 0.016919999999999998, 'prompt_tokens': 558, 'completion_tokens': 3, 'total_tokens': 561}}, {'total_cost': 0}), human_input=[])" ] }, - "execution_count": 5, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } From eefb51c231206f570bb3b20048b59320caac144e Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Wed, 21 Feb 2024 14:53:27 -0600 Subject: [PATCH 37/57] update notebook and test --- notebook/agentchat_nestedchat.ipynb | 499 +++++++++++----------------- test/agentchat/test_chats.py | 6 +- test/agentchat/test_nested.py | 237 +++---------- 3 files changed, 254 insertions(+), 488 deletions(-) diff --git a/notebook/agentchat_nestedchat.ipynb b/notebook/agentchat_nestedchat.ipynb index 2740d53367d..464e2a5646c 100644 --- a/notebook/agentchat_nestedchat.ipynb +++ b/notebook/agentchat_nestedchat.ipynb @@ -31,17 +31,9 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 9, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Neither powershell nor pwsh is installed.\n" - ] - } - ], + "outputs": [], "source": [ "import autogen\n", "\n", @@ -50,13 +42,13 @@ "\n", "tasks = [\n", " \"\"\"On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.\"\"\",\n", - " \"\"\"Investigate possible reasons for the performance.\"\"\",\n", + " \"\"\"Make a pleasant joke about it.\"\"\",\n", "]" ] }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 10, "metadata": {}, "outputs": [], "source": [ @@ -98,7 +90,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -150,7 +142,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 12, "metadata": {}, "outputs": [ { @@ -296,18 +288,23 @@ "Date: 2024-02-15, Close: $406.56\n", "Date: 2024-02-16, Close: $404.06\n", "Date: 2024-02-20, Close: $402.79\n", + "Date: 2024-02-21, Close: $400.38\n", "\n", - "Microsoft stock was higher than $400 on 18 days between 2024-01-24 and 2024-02-20 in 2024.\n", + "Microsoft stock was higher than $400 on 19 days between 2024-01-24 and 2024-02-21 in 2024.\n", "\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", "\n", - "Based on the stock data retrieved, Microsoft stock (ticker symbol: MSFT) traded higher than $400 on 18 days between January 24, 2024, and February 20, 2024. The highest closing price during this period was $419.77 on February 9, 2024. \n", + "The execution of the provided code indicates that Microsoft stock (MSFT) was trading higher than $400 on 19 days between January 24, 2024, and February 21, 2024. Here is an overview of the stock performance during this period:\n", "\n", - "This indicates a period of strong performance for Microsoft's stock at the beginning of the year 2024, with multiple trading days closing above the $400 mark. It suggests that investors valued the company at a premium during this time frame, potentially due to positive market sentiment, strong financial results, or other favorable news affecting the stock.\n", + "- **Peak Price**: The highest closing price recorded was $419.77 on February 9, 2024.\n", + "- **Stability Above $400**: The stock maintained its value above $400 on multiple consecutive days.\n", + "- **Volatility**: There were fluctuations in the stock price, with it crossing the $400 threshold multiple times.\n", "\n", - "To comment on the overall stock performance for the whole year, we would need to observe the stock price movement across all 12 months, as well as compare it to the company’s financials, market trends, and other relevant factors that could impact the stock value. Nonetheless, for the dates provided, it shows a bullish period for Microsoft's stock in early 2024.\n", + "Based on the output, we can infer that during the start of 2024, MSFT experienced periods of strong performance with its share price reaching above the $400 mark, highlighting investor confidence or positive market conditions for the company at that time. However, without further contextual information or a comparison over a more extended period, it is challenging to make a comprehensive assessment of the stock's overall performance.\n", + "\n", + "For a more detailed analysis, you would typically look at additional information such as the company's earnings reports, news, market trends, and broader economic indicators. Overall, the short window provided suggests a positive trend for Microsoft during the early part of 2024.\n", "\n", "TERMINATE\n", "\n", @@ -320,7 +317,7 @@ " On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.\n", "\n", "With the following carryover: \n", - "Microsoft stock traded higher than $400 on 18 days between January 24, 2024, and February 20, 2024, indicating a strong performance at the beginning of the year. The highest closing price was $419.77 on February 9, 2024. The bullish period could be attributed to positive market sentiment, strong financial results, or favorable company news. However, a full assessment of the stock’s annual performance would require analysis throughout the entire year.\u001b[0m\n", + "The Python code execution shows Microsoft stock (MSFT) exceeded $400 on 19 days from January 24, 2024, to February 21, 2024, with the highest closing price being $419.77 on February 9, 2024. The stock displayed periods of solid performance and stability above $400 during this timeframe, indicating positive market conditions or investor confidence in Microsoft's performance at the start of 2024.\u001b[0m\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", "\u001b[33mAssistant_1\u001b[0m (to Writer):\n", @@ -329,37 +326,26 @@ "\n", " On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.\n", "Context: \n", - "Microsoft stock traded higher than $400 on 18 days between January 24, 2024, and February 20, 2024, indicating a strong performance at the beginning of the year. The highest closing price was $419.77 on February 9, 2024. The bullish period could be attributed to positive market sentiment, strong financial results, or favorable company news. However, a full assessment of the stock’s annual performance would require analysis throughout the entire year.\n", + "The Python code execution shows Microsoft stock (MSFT) exceeded $400 on 19 days from January 24, 2024, to February 21, 2024, with the highest closing price being $419.77 on February 9, 2024. The stock displayed periods of solid performance and stability above $400 during this timeframe, indicating positive market conditions or investor confidence in Microsoft's performance at the start of 2024.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mWriter\u001b[0m (to Assistant_1):\n", "\n", - "# Climbing the Summit: Microsoft Stock's Ascent Above $400 in Early 2024\n", - "\n", - "In the opening act of 2024, Microsoft's stock was akin to a valiant climber, relentlessly ascending the market's steep cliffs. With the grace of a seasoned mountaineer, it scaled heights surpassing the $400 mark—an impressive feat captured on 18 distinct occasions between January 24, 2024, and February 20, 2024. This series of peaks in Microsoft's financial landscape is not merely a number—it's a testament to the company's robust performance as the new year unfolded.\n", + "Title: **Microsoft's Ascension: Stock Soars Above $400 in Early 2024**\n", "\n", - "### The Days of Triumph\n", + "In the upper echelons of the stock market, Microsoft Corporation (MSFT) stands as a gauge of innovative prowess and economic vitality. Its shares are more than just pieces of paper; they represent the confidence and aspirations of investors worldwide. It takes a remarkable convergence of market conditions and corporate triumphs for a stock to breach a milestone as significant as the $400 mark. Microsoft stock not only surpassed this threshold but did so with exceptional poise, ushering in a period of celebration for shareholders in the early months of 2024.\n", "\n", - "Investors and market enthusiasts watched with bated breath as Microsoft shares rose, cresting $400 with the ease of a tech giant aware of its prowess. These moments of triumph were not fleeting; they laid a strong foundation for what might be interpreted as bullish days ahead:\n", + "Between January 24th and February 21st, Microsoft’s stock price proudly exceeded the $400 benchmark on 19 different occasions. This was not a fleeting brush with success but a testament to the tech giant’s enduring magnetism in the market. The crescendo of this symphony of gains was the stunning closing price of $419.77 on February 9th, marking the pinnacle of Microsoft’s financial performance during this period.\n", "\n", - "- The stock broke the $400 barrier, acting as both a support level in traders' minds and a signal of the company's market strength.\n", - "- The pinnacle of this period was reached on February 9, 2024, with the stock closing at an all-time daily high of $419.77—a beacon of the company's financial vitality.\n", + "As the graph of MSFT traced an enviable trajectory, market analysts and investors alike were reminded of the intrinsic strength bolstering Microsoft’s market position. This impressive trend was no accident; it was the result of a carefully balanced equation of innovation, strategic leadership, and market dynamics. Each day above $400 whispered a story of unwavering trust in Microsoft's trajectory and an underlying confidence in its ability to leverage opportunities, whether they lay in cloud computing, artificial intelligence, gaming, or productivity tools.\n", "\n", - "### Deciphering the Ascent\n", + "The persistent performance of MSFT not only indicated the vitality of Microsoft as a corporation but also reflected wider economic currents. In the dance of the stock market, every jump and dip is a response to the music played by investor sentiment, technological advancements, and global economic trends. To stay above $400 for a sustained period is to ride the crest of these forces, showcasing an alignment with the progress investors believe technology will undeniably drive in our everyday lives.\n", "\n", - "What fueled this upward drive? When a stock like Microsoft's sails beyond a threshold such as $400, the reasons may be as multifaceted as the company's own product line:\n", + "As we dissect these moments of triumph, we find the essence of Microsoft's financial fortitude. It's a blend of continuous innovation, well-calibrated risk management, and the company's intrinsic ability to evolve in the fast-paced high-tech arena. With products and services that have achieved a ubiquitous presence, Microsoft has been sculpting the future, byte by byte, in a world voraciously hungry for digital transformation.\n", "\n", - "- **Positive Market Sentiment:** The market's general mood can often tip the scales. If investors are optimistic, even the sky isn't a limit. \n", - "- **Strong Financial Results:** Like a report card that every investor scrutinizes, Microsoft's financials must have shone, signaling health and profitability.\n", - "- **Favorable Company News:** An announcement, a groundbreaking product release, or an influential acquisition might have been the wind beneath the company's wings.\n", + "The million-dollar question remains: Can Microsoft maintain this stellar performance throughout 2024? While forecasts are invariably tinged with uncertainty, if the company's illustrious record of targeting burgeoning markets and expanding its influence is anything to go by, then anticipation is in the air. Investors watch with bated breath, pondering whether this IT titan will continue to climb or find this peak soon eclipsed by even higher summits.\n", "\n", - "### A Year in the Making\n", - "\n", - "While the early elevation of Microsoft's stock price sets a tone of confidence, it is but a chapter in the annals of an entire year's performance. To truly understand the narrative of Microsoft in 2024, one would need to turn every page of the calendar, scrutinizing the ebb and flow of its stock with a keen eye on the events that sway the market.\n", - "\n", - "In the splendor of its early achievements, Microsoft seemed to be painting a hopeful picture for 2024. Yet, the true masterpiece of its annual performance will only come into full view with time and the unfolding of the market's complex tapestry. Whether the early rise will be a prelude to a sustained ascent or a peak before a valley remains a story yet to be told.\n", - "\n", - "In conclusion, the days where Microsoft's stock stood tall above $400 signal not just a strong start but also pose intriguing questions about the sustainability of such performance. As history has often shown us, the stock market is a realm of intrigue, where each fluctuation tells a story, and every peak is a prelude to the next great adventure in finance. Stay tuned, for the market writes its own epic, and Microsoft's stock is but one of its many protagonists.\n", + "As we turn the page on this chapter of Microsoft's fiscal saga, the only certainty is that the journey up the stock charts is as dynamic as the technologies the company masterfully crafts. Will the stock continue its flight, or will it face the gravity of market corrections? Only time will tell, but for now, Microsoft has given its investors reason to bask in the warm glow of market triumph.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[34m\n", @@ -368,97 +354,79 @@ "Review the content provided.\n", "\n", "With the following carryover: \n", - "Microsoft stock traded higher than $400 on 18 days between January 24, 2024, and February 20, 2024, indicating a strong performance at the beginning of the year. The highest closing price was $419.77 on February 9, 2024. The bullish period could be attributed to positive market sentiment, strong financial results, or favorable company news. However, a full assessment of the stock’s annual performance would require analysis throughout the entire year.\n", - "# Climbing the Summit: Microsoft Stock's Ascent Above $400 in Early 2024\n", - "\n", - "In the opening act of 2024, Microsoft's stock was akin to a valiant climber, relentlessly ascending the market's steep cliffs. With the grace of a seasoned mountaineer, it scaled heights surpassing the $400 mark—an impressive feat captured on 18 distinct occasions between January 24, 2024, and February 20, 2024. This series of peaks in Microsoft's financial landscape is not merely a number—it's a testament to the company's robust performance as the new year unfolded.\n", - "\n", - "### The Days of Triumph\n", + "The Python code execution shows Microsoft stock (MSFT) exceeded $400 on 19 days from January 24, 2024, to February 21, 2024, with the highest closing price being $419.77 on February 9, 2024. The stock displayed periods of solid performance and stability above $400 during this timeframe, indicating positive market conditions or investor confidence in Microsoft's performance at the start of 2024.\n", + "Title: **Microsoft's Ascension: Stock Soars Above $400 in Early 2024**\n", "\n", - "Investors and market enthusiasts watched with bated breath as Microsoft shares rose, cresting $400 with the ease of a tech giant aware of its prowess. These moments of triumph were not fleeting; they laid a strong foundation for what might be interpreted as bullish days ahead:\n", + "In the upper echelons of the stock market, Microsoft Corporation (MSFT) stands as a gauge of innovative prowess and economic vitality. Its shares are more than just pieces of paper; they represent the confidence and aspirations of investors worldwide. It takes a remarkable convergence of market conditions and corporate triumphs for a stock to breach a milestone as significant as the $400 mark. Microsoft stock not only surpassed this threshold but did so with exceptional poise, ushering in a period of celebration for shareholders in the early months of 2024.\n", "\n", - "- The stock broke the $400 barrier, acting as both a support level in traders' minds and a signal of the company's market strength.\n", - "- The pinnacle of this period was reached on February 9, 2024, with the stock closing at an all-time daily high of $419.77—a beacon of the company's financial vitality.\n", + "Between January 24th and February 21st, Microsoft’s stock price proudly exceeded the $400 benchmark on 19 different occasions. This was not a fleeting brush with success but a testament to the tech giant’s enduring magnetism in the market. The crescendo of this symphony of gains was the stunning closing price of $419.77 on February 9th, marking the pinnacle of Microsoft’s financial performance during this period.\n", "\n", - "### Deciphering the Ascent\n", + "As the graph of MSFT traced an enviable trajectory, market analysts and investors alike were reminded of the intrinsic strength bolstering Microsoft’s market position. This impressive trend was no accident; it was the result of a carefully balanced equation of innovation, strategic leadership, and market dynamics. Each day above $400 whispered a story of unwavering trust in Microsoft's trajectory and an underlying confidence in its ability to leverage opportunities, whether they lay in cloud computing, artificial intelligence, gaming, or productivity tools.\n", "\n", - "What fueled this upward drive? When a stock like Microsoft's sails beyond a threshold such as $400, the reasons may be as multifaceted as the company's own product line:\n", + "The persistent performance of MSFT not only indicated the vitality of Microsoft as a corporation but also reflected wider economic currents. In the dance of the stock market, every jump and dip is a response to the music played by investor sentiment, technological advancements, and global economic trends. To stay above $400 for a sustained period is to ride the crest of these forces, showcasing an alignment with the progress investors believe technology will undeniably drive in our everyday lives.\n", "\n", - "- **Positive Market Sentiment:** The market's general mood can often tip the scales. If investors are optimistic, even the sky isn't a limit. \n", - "- **Strong Financial Results:** Like a report card that every investor scrutinizes, Microsoft's financials must have shone, signaling health and profitability.\n", - "- **Favorable Company News:** An announcement, a groundbreaking product release, or an influential acquisition might have been the wind beneath the company's wings.\n", + "As we dissect these moments of triumph, we find the essence of Microsoft's financial fortitude. It's a blend of continuous innovation, well-calibrated risk management, and the company's intrinsic ability to evolve in the fast-paced high-tech arena. With products and services that have achieved a ubiquitous presence, Microsoft has been sculpting the future, byte by byte, in a world voraciously hungry for digital transformation.\n", "\n", - "### A Year in the Making\n", + "The million-dollar question remains: Can Microsoft maintain this stellar performance throughout 2024? While forecasts are invariably tinged with uncertainty, if the company's illustrious record of targeting burgeoning markets and expanding its influence is anything to go by, then anticipation is in the air. Investors watch with bated breath, pondering whether this IT titan will continue to climb or find this peak soon eclipsed by even higher summits.\n", "\n", - "While the early elevation of Microsoft's stock price sets a tone of confidence, it is but a chapter in the annals of an entire year's performance. To truly understand the narrative of Microsoft in 2024, one would need to turn every page of the calendar, scrutinizing the ebb and flow of its stock with a keen eye on the events that sway the market.\n", - "\n", - "In the splendor of its early achievements, Microsoft seemed to be painting a hopeful picture for 2024. Yet, the true masterpiece of its annual performance will only come into full view with time and the unfolding of the market's complex tapestry. Whether the early rise will be a prelude to a sustained ascent or a peak before a valley remains a story yet to be told.\n", - "\n", - "In conclusion, the days where Microsoft's stock stood tall above $400 signal not just a strong start but also pose intriguing questions about the sustainability of such performance. As history has often shown us, the stock market is a realm of intrigue, where each fluctuation tells a story, and every peak is a prelude to the next great adventure in finance. Stay tuned, for the market writes its own epic, and Microsoft's stock is but one of its many protagonists.\u001b[0m\n", + "As we turn the page on this chapter of Microsoft's fiscal saga, the only certainty is that the journey up the stock charts is as dynamic as the technologies the company masterfully crafts. Will the stock continue its flight, or will it face the gravity of market corrections? Only time will tell, but for now, Microsoft has given its investors reason to bask in the warm glow of market triumph.\u001b[0m\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", "\u001b[33mAssistant_1\u001b[0m (to Reviewer):\n", "\n", "Review the content provided.\n", "Context: \n", - "Microsoft stock traded higher than $400 on 18 days between January 24, 2024, and February 20, 2024, indicating a strong performance at the beginning of the year. The highest closing price was $419.77 on February 9, 2024. The bullish period could be attributed to positive market sentiment, strong financial results, or favorable company news. However, a full assessment of the stock’s annual performance would require analysis throughout the entire year.\n", - "# Climbing the Summit: Microsoft Stock's Ascent Above $400 in Early 2024\n", - "\n", - "In the opening act of 2024, Microsoft's stock was akin to a valiant climber, relentlessly ascending the market's steep cliffs. With the grace of a seasoned mountaineer, it scaled heights surpassing the $400 mark—an impressive feat captured on 18 distinct occasions between January 24, 2024, and February 20, 2024. This series of peaks in Microsoft's financial landscape is not merely a number—it's a testament to the company's robust performance as the new year unfolded.\n", - "\n", - "### The Days of Triumph\n", - "\n", - "Investors and market enthusiasts watched with bated breath as Microsoft shares rose, cresting $400 with the ease of a tech giant aware of its prowess. These moments of triumph were not fleeting; they laid a strong foundation for what might be interpreted as bullish days ahead:\n", + "The Python code execution shows Microsoft stock (MSFT) exceeded $400 on 19 days from January 24, 2024, to February 21, 2024, with the highest closing price being $419.77 on February 9, 2024. The stock displayed periods of solid performance and stability above $400 during this timeframe, indicating positive market conditions or investor confidence in Microsoft's performance at the start of 2024.\n", + "Title: **Microsoft's Ascension: Stock Soars Above $400 in Early 2024**\n", "\n", - "- The stock broke the $400 barrier, acting as both a support level in traders' minds and a signal of the company's market strength.\n", - "- The pinnacle of this period was reached on February 9, 2024, with the stock closing at an all-time daily high of $419.77—a beacon of the company's financial vitality.\n", + "In the upper echelons of the stock market, Microsoft Corporation (MSFT) stands as a gauge of innovative prowess and economic vitality. Its shares are more than just pieces of paper; they represent the confidence and aspirations of investors worldwide. It takes a remarkable convergence of market conditions and corporate triumphs for a stock to breach a milestone as significant as the $400 mark. Microsoft stock not only surpassed this threshold but did so with exceptional poise, ushering in a period of celebration for shareholders in the early months of 2024.\n", "\n", - "### Deciphering the Ascent\n", + "Between January 24th and February 21st, Microsoft’s stock price proudly exceeded the $400 benchmark on 19 different occasions. This was not a fleeting brush with success but a testament to the tech giant’s enduring magnetism in the market. The crescendo of this symphony of gains was the stunning closing price of $419.77 on February 9th, marking the pinnacle of Microsoft’s financial performance during this period.\n", "\n", - "What fueled this upward drive? When a stock like Microsoft's sails beyond a threshold such as $400, the reasons may be as multifaceted as the company's own product line:\n", + "As the graph of MSFT traced an enviable trajectory, market analysts and investors alike were reminded of the intrinsic strength bolstering Microsoft’s market position. This impressive trend was no accident; it was the result of a carefully balanced equation of innovation, strategic leadership, and market dynamics. Each day above $400 whispered a story of unwavering trust in Microsoft's trajectory and an underlying confidence in its ability to leverage opportunities, whether they lay in cloud computing, artificial intelligence, gaming, or productivity tools.\n", "\n", - "- **Positive Market Sentiment:** The market's general mood can often tip the scales. If investors are optimistic, even the sky isn't a limit. \n", - "- **Strong Financial Results:** Like a report card that every investor scrutinizes, Microsoft's financials must have shone, signaling health and profitability.\n", - "- **Favorable Company News:** An announcement, a groundbreaking product release, or an influential acquisition might have been the wind beneath the company's wings.\n", + "The persistent performance of MSFT not only indicated the vitality of Microsoft as a corporation but also reflected wider economic currents. In the dance of the stock market, every jump and dip is a response to the music played by investor sentiment, technological advancements, and global economic trends. To stay above $400 for a sustained period is to ride the crest of these forces, showcasing an alignment with the progress investors believe technology will undeniably drive in our everyday lives.\n", "\n", - "### A Year in the Making\n", + "As we dissect these moments of triumph, we find the essence of Microsoft's financial fortitude. It's a blend of continuous innovation, well-calibrated risk management, and the company's intrinsic ability to evolve in the fast-paced high-tech arena. With products and services that have achieved a ubiquitous presence, Microsoft has been sculpting the future, byte by byte, in a world voraciously hungry for digital transformation.\n", "\n", - "While the early elevation of Microsoft's stock price sets a tone of confidence, it is but a chapter in the annals of an entire year's performance. To truly understand the narrative of Microsoft in 2024, one would need to turn every page of the calendar, scrutinizing the ebb and flow of its stock with a keen eye on the events that sway the market.\n", + "The million-dollar question remains: Can Microsoft maintain this stellar performance throughout 2024? While forecasts are invariably tinged with uncertainty, if the company's illustrious record of targeting burgeoning markets and expanding its influence is anything to go by, then anticipation is in the air. Investors watch with bated breath, pondering whether this IT titan will continue to climb or find this peak soon eclipsed by even higher summits.\n", "\n", - "In the splendor of its early achievements, Microsoft seemed to be painting a hopeful picture for 2024. Yet, the true masterpiece of its annual performance will only come into full view with time and the unfolding of the market's complex tapestry. Whether the early rise will be a prelude to a sustained ascent or a peak before a valley remains a story yet to be told.\n", - "\n", - "In conclusion, the days where Microsoft's stock stood tall above $400 signal not just a strong start but also pose intriguing questions about the sustainability of such performance. As history has often shown us, the stock market is a realm of intrigue, where each fluctuation tells a story, and every peak is a prelude to the next great adventure in finance. Stay tuned, for the market writes its own epic, and Microsoft's stock is but one of its many protagonists.\n", + "As we turn the page on this chapter of Microsoft's fiscal saga, the only certainty is that the journey up the stock charts is as dynamic as the technologies the company masterfully crafts. Will the stock continue its flight, or will it face the gravity of market corrections? Only time will tell, but for now, Microsoft has given its investors reason to bask in the warm glow of market triumph.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mReviewer\u001b[0m (to Assistant_1):\n", "\n", - "The provided content appears to be a financial commentary or news article discussing the performance of Microsoft's stock in early 2024. It provides factual information about Microsoft's stock prices and offers speculative insight into the possible reasons behind the stock's performance, as well as caution regarding the uncertainty of future market behavior.\n", + "As a compliance reviewer, my key focus in examining the provided content is to ensure that the material does not contain any misinformation, unsubstantiated claims, regulatory violations, or language that could be deemed harmful or misleading to consumers or investors. The review also includes a check for broader ethical considerations such as fairness, bias, and language appropriateness.\n", "\n", - "Upon reviewing the content, here are the considerations to ensure compliance and the absence of harmful elements:\n", + "Review Summary:\n", "\n", - "1. **Accuracy of Information**: The article mentions specific dates and stock prices which need to be accurate and verifiable. Falsifying or misrepresenting financial data could lead to serious regulatory violations and mislead investors.\n", + "1. **Accuracy of Information**: The content references specific dates and stock prices which are factual claims. These should be cross-verified with actual market data to ensure the information is correct. False or outdated information could mislead investors.\n", "\n", - "2. **Forward-Looking Statements**: When discussing potential future performance (\"bullish days ahead\", \"whether the early rise will be a prelude to a sustained ascent\"), it should be clear to readers that these are speculative and not guarantees. It is prudent to include disclaimers regarding forward-looking statements to avoid misleading investors per securities law.\n", + "2. **Investment Advice and Predictions**: The content should avoid making predictions or offering investment advice unless it is a regulated advisor with the appropriate disclaimers. Phrases like the \"million-dollar question\" and speculating on future performance need to be examined within the context of providing financial advice.\n", "\n", - "3. **Investment Advice**: The article should not be construed as giving specific investment advice unless it is coming from a licensed professional authorized to do so. Ensure that the language used does not directly endorse buying or selling specific securities.\n", + "3. **Forward-looking Statements**: The content includes forward-looking statements about Microsoft's future performance. It's important to include a disclaimer that past performance is not indicative of future results, to ensure compliance with financial communication regulations.\n", "\n", - "4. **Fairness and Balance**: The article should present a balanced view, acknowledging both potential upsides and risks. It should avoid overemphasizing the positive aspects without considering factors that could negatively impact the stock’s future performance.\n", + "4. **Promotional Language**: The title \"Microsoft's Ascension\" and descriptive phrases like \"ushering in a period of celebration for shareholders\" might be too promotional and could be seen as encouraging investment. The content should present a balanced view without overly promotional language.\n", "\n", - "5. **Use of Metaphors**: The article employs metaphors (e.g., \"akin to a valiant climber\", \"the market's steep cliffs\") which are acceptable as long as they don't confuse or mislead the readers about the financial realities being discussed.\n", + "5. **Balance and Fairness**: While the content praises Microsoft's performance, it should also fairly acknowledge market risks and realities without guaranteeing future results. Trust and confidence statements must be substantiated and not imply unrealistically high expectations.\n", "\n", - "6. **Promotional Content**: Ensure that the article is not serving as promotional material for Microsoft or any associated entities without appropriate disclosures.\n", + "6. **Use of Metaphors and Language**: The content is rich with metaphors and expressive language (e.g., \"symphony of gains,\" \"dance of the stock market\"). While engaging, caution is warranted to ensure that the metaphors do not obscure facts, mislead, or overstate the stability and potential of an investment.\n", "\n", - "7. **Conflicts of Interest**: Disclose any potential conflicts of interest that the publisher of the content may have with Microsoft.\n", + "7. **Market Manipulation**: There appears to be no intent to manipulate the market, but the optimistic tone could unduly influence investor decisions. A compliance reviewer should ensure the statements have basis and are not intended to artificially inflate stock value.\n", "\n", - "8. **Regulatory Compliance**: Verify that the content aligns with the relevant regulatory requirements, such as those set forth by the SEC or other financial regulatory authorities that govern financial reporting and communications.\n", + "8. **Disclosure of Interests**: If the content creator or publisher holds interests in Microsoft, this should be disclosed to avoid conflicts of interest and ensure compliance with regulations such as the SEC’s rules on fair disclosure.\n", "\n", - "9. **Intellectual Property**: Verify that any content has not infringed on the intellectual property rights of others.\n", + "Recommendations:\n", "\n", - "10. **Market Manipulation**: Ensure that there are no attempts to manipulate the market by presenting information in a biased or coercive manner.\n", + "- Ensure all factual claims are cross-verified with market data.\n", + "- If providing forward-looking statements, include the necessary disclaimers about market risks and the unpredictability of stock performance.\n", + "- Temper promotional language to provide a balanced viewpoint, avoiding the potential perception of investment advice.\n", + "- Avoid strong implications regarding investment decisions which are not supported by substantial evidence.\n", + "- Check if the content creator or publisher has any vested interest in Microsoft and ensure proper disclosures are included.\n", "\n", - "From the provided content, there don't appear to be explicit violations; however, thorough fact-checking and the addition of appropriate disclaimers would be needed to fully comply with regulations. The speculative and forward-looking statements should be clearly marked as such to avoid any misunderstandings regarding the nature of the content. \n", + "Conclusion:\n", "\n", - "It is advisable for financial articles of this nature to remind readers to perform their own due diligence and remind them that past performance is not indicative of future results. If all these aspects are thoroughly checked and addressed, the content could be considered compliant and free from harmful elements.\n", + "The content should satisfactorily meet compliance standards after careful verification of the facts, balancing language, and including necessary disclaimers and disclosures. The intention to avoid causing harm or misleading readers while ensuring fairness and compliance with regulations should guide any revisions.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[34m\n", @@ -469,60 +437,53 @@ " On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.\n", "\n", "With the following carryover: \n", - "Microsoft stock traded higher than $400 on 18 days between January 24, 2024, and February 20, 2024, indicating a strong performance at the beginning of the year. The highest closing price was $419.77 on February 9, 2024. The bullish period could be attributed to positive market sentiment, strong financial results, or favorable company news. However, a full assessment of the stock’s annual performance would require analysis throughout the entire year.\n", - "# Climbing the Summit: Microsoft Stock's Ascent Above $400 in Early 2024\n", - "\n", - "In the opening act of 2024, Microsoft's stock was akin to a valiant climber, relentlessly ascending the market's steep cliffs. With the grace of a seasoned mountaineer, it scaled heights surpassing the $400 mark—an impressive feat captured on 18 distinct occasions between January 24, 2024, and February 20, 2024. This series of peaks in Microsoft's financial landscape is not merely a number—it's a testament to the company's robust performance as the new year unfolded.\n", - "\n", - "### The Days of Triumph\n", + "The Python code execution shows Microsoft stock (MSFT) exceeded $400 on 19 days from January 24, 2024, to February 21, 2024, with the highest closing price being $419.77 on February 9, 2024. The stock displayed periods of solid performance and stability above $400 during this timeframe, indicating positive market conditions or investor confidence in Microsoft's performance at the start of 2024.\n", + "Title: **Microsoft's Ascension: Stock Soars Above $400 in Early 2024**\n", "\n", - "Investors and market enthusiasts watched with bated breath as Microsoft shares rose, cresting $400 with the ease of a tech giant aware of its prowess. These moments of triumph were not fleeting; they laid a strong foundation for what might be interpreted as bullish days ahead:\n", + "In the upper echelons of the stock market, Microsoft Corporation (MSFT) stands as a gauge of innovative prowess and economic vitality. Its shares are more than just pieces of paper; they represent the confidence and aspirations of investors worldwide. It takes a remarkable convergence of market conditions and corporate triumphs for a stock to breach a milestone as significant as the $400 mark. Microsoft stock not only surpassed this threshold but did so with exceptional poise, ushering in a period of celebration for shareholders in the early months of 2024.\n", "\n", - "- The stock broke the $400 barrier, acting as both a support level in traders' minds and a signal of the company's market strength.\n", - "- The pinnacle of this period was reached on February 9, 2024, with the stock closing at an all-time daily high of $419.77—a beacon of the company's financial vitality.\n", + "Between January 24th and February 21st, Microsoft’s stock price proudly exceeded the $400 benchmark on 19 different occasions. This was not a fleeting brush with success but a testament to the tech giant’s enduring magnetism in the market. The crescendo of this symphony of gains was the stunning closing price of $419.77 on February 9th, marking the pinnacle of Microsoft’s financial performance during this period.\n", "\n", - "### Deciphering the Ascent\n", + "As the graph of MSFT traced an enviable trajectory, market analysts and investors alike were reminded of the intrinsic strength bolstering Microsoft’s market position. This impressive trend was no accident; it was the result of a carefully balanced equation of innovation, strategic leadership, and market dynamics. Each day above $400 whispered a story of unwavering trust in Microsoft's trajectory and an underlying confidence in its ability to leverage opportunities, whether they lay in cloud computing, artificial intelligence, gaming, or productivity tools.\n", "\n", - "What fueled this upward drive? When a stock like Microsoft's sails beyond a threshold such as $400, the reasons may be as multifaceted as the company's own product line:\n", + "The persistent performance of MSFT not only indicated the vitality of Microsoft as a corporation but also reflected wider economic currents. In the dance of the stock market, every jump and dip is a response to the music played by investor sentiment, technological advancements, and global economic trends. To stay above $400 for a sustained period is to ride the crest of these forces, showcasing an alignment with the progress investors believe technology will undeniably drive in our everyday lives.\n", "\n", - "- **Positive Market Sentiment:** The market's general mood can often tip the scales. If investors are optimistic, even the sky isn't a limit. \n", - "- **Strong Financial Results:** Like a report card that every investor scrutinizes, Microsoft's financials must have shone, signaling health and profitability.\n", - "- **Favorable Company News:** An announcement, a groundbreaking product release, or an influential acquisition might have been the wind beneath the company's wings.\n", + "As we dissect these moments of triumph, we find the essence of Microsoft's financial fortitude. It's a blend of continuous innovation, well-calibrated risk management, and the company's intrinsic ability to evolve in the fast-paced high-tech arena. With products and services that have achieved a ubiquitous presence, Microsoft has been sculpting the future, byte by byte, in a world voraciously hungry for digital transformation.\n", "\n", - "### A Year in the Making\n", + "The million-dollar question remains: Can Microsoft maintain this stellar performance throughout 2024? While forecasts are invariably tinged with uncertainty, if the company's illustrious record of targeting burgeoning markets and expanding its influence is anything to go by, then anticipation is in the air. Investors watch with bated breath, pondering whether this IT titan will continue to climb or find this peak soon eclipsed by even higher summits.\n", "\n", - "While the early elevation of Microsoft's stock price sets a tone of confidence, it is but a chapter in the annals of an entire year's performance. To truly understand the narrative of Microsoft in 2024, one would need to turn every page of the calendar, scrutinizing the ebb and flow of its stock with a keen eye on the events that sway the market.\n", + "As we turn the page on this chapter of Microsoft's fiscal saga, the only certainty is that the journey up the stock charts is as dynamic as the technologies the company masterfully crafts. Will the stock continue its flight, or will it face the gravity of market corrections? Only time will tell, but for now, Microsoft has given its investors reason to bask in the warm glow of market triumph.\n", + "As a compliance reviewer, my key focus in examining the provided content is to ensure that the material does not contain any misinformation, unsubstantiated claims, regulatory violations, or language that could be deemed harmful or misleading to consumers or investors. The review also includes a check for broader ethical considerations such as fairness, bias, and language appropriateness.\n", "\n", - "In the splendor of its early achievements, Microsoft seemed to be painting a hopeful picture for 2024. Yet, the true masterpiece of its annual performance will only come into full view with time and the unfolding of the market's complex tapestry. Whether the early rise will be a prelude to a sustained ascent or a peak before a valley remains a story yet to be told.\n", + "Review Summary:\n", "\n", - "In conclusion, the days where Microsoft's stock stood tall above $400 signal not just a strong start but also pose intriguing questions about the sustainability of such performance. As history has often shown us, the stock market is a realm of intrigue, where each fluctuation tells a story, and every peak is a prelude to the next great adventure in finance. Stay tuned, for the market writes its own epic, and Microsoft's stock is but one of its many protagonists.\n", - "The provided content appears to be a financial commentary or news article discussing the performance of Microsoft's stock in early 2024. It provides factual information about Microsoft's stock prices and offers speculative insight into the possible reasons behind the stock's performance, as well as caution regarding the uncertainty of future market behavior.\n", + "1. **Accuracy of Information**: The content references specific dates and stock prices which are factual claims. These should be cross-verified with actual market data to ensure the information is correct. False or outdated information could mislead investors.\n", "\n", - "Upon reviewing the content, here are the considerations to ensure compliance and the absence of harmful elements:\n", + "2. **Investment Advice and Predictions**: The content should avoid making predictions or offering investment advice unless it is a regulated advisor with the appropriate disclaimers. Phrases like the \"million-dollar question\" and speculating on future performance need to be examined within the context of providing financial advice.\n", "\n", - "1. **Accuracy of Information**: The article mentions specific dates and stock prices which need to be accurate and verifiable. Falsifying or misrepresenting financial data could lead to serious regulatory violations and mislead investors.\n", + "3. **Forward-looking Statements**: The content includes forward-looking statements about Microsoft's future performance. It's important to include a disclaimer that past performance is not indicative of future results, to ensure compliance with financial communication regulations.\n", "\n", - "2. **Forward-Looking Statements**: When discussing potential future performance (\"bullish days ahead\", \"whether the early rise will be a prelude to a sustained ascent\"), it should be clear to readers that these are speculative and not guarantees. It is prudent to include disclaimers regarding forward-looking statements to avoid misleading investors per securities law.\n", + "4. **Promotional Language**: The title \"Microsoft's Ascension\" and descriptive phrases like \"ushering in a period of celebration for shareholders\" might be too promotional and could be seen as encouraging investment. The content should present a balanced view without overly promotional language.\n", "\n", - "3. **Investment Advice**: The article should not be construed as giving specific investment advice unless it is coming from a licensed professional authorized to do so. Ensure that the language used does not directly endorse buying or selling specific securities.\n", + "5. **Balance and Fairness**: While the content praises Microsoft's performance, it should also fairly acknowledge market risks and realities without guaranteeing future results. Trust and confidence statements must be substantiated and not imply unrealistically high expectations.\n", "\n", - "4. **Fairness and Balance**: The article should present a balanced view, acknowledging both potential upsides and risks. It should avoid overemphasizing the positive aspects without considering factors that could negatively impact the stock’s future performance.\n", + "6. **Use of Metaphors and Language**: The content is rich with metaphors and expressive language (e.g., \"symphony of gains,\" \"dance of the stock market\"). While engaging, caution is warranted to ensure that the metaphors do not obscure facts, mislead, or overstate the stability and potential of an investment.\n", "\n", - "5. **Use of Metaphors**: The article employs metaphors (e.g., \"akin to a valiant climber\", \"the market's steep cliffs\") which are acceptable as long as they don't confuse or mislead the readers about the financial realities being discussed.\n", + "7. **Market Manipulation**: There appears to be no intent to manipulate the market, but the optimistic tone could unduly influence investor decisions. A compliance reviewer should ensure the statements have basis and are not intended to artificially inflate stock value.\n", "\n", - "6. **Promotional Content**: Ensure that the article is not serving as promotional material for Microsoft or any associated entities without appropriate disclosures.\n", + "8. **Disclosure of Interests**: If the content creator or publisher holds interests in Microsoft, this should be disclosed to avoid conflicts of interest and ensure compliance with regulations such as the SEC’s rules on fair disclosure.\n", "\n", - "7. **Conflicts of Interest**: Disclose any potential conflicts of interest that the publisher of the content may have with Microsoft.\n", + "Recommendations:\n", "\n", - "8. **Regulatory Compliance**: Verify that the content aligns with the relevant regulatory requirements, such as those set forth by the SEC or other financial regulatory authorities that govern financial reporting and communications.\n", + "- Ensure all factual claims are cross-verified with market data.\n", + "- If providing forward-looking statements, include the necessary disclaimers about market risks and the unpredictability of stock performance.\n", + "- Temper promotional language to provide a balanced viewpoint, avoiding the potential perception of investment advice.\n", + "- Avoid strong implications regarding investment decisions which are not supported by substantial evidence.\n", + "- Check if the content creator or publisher has any vested interest in Microsoft and ensure proper disclosures are included.\n", "\n", - "9. **Intellectual Property**: Verify that any content has not infringed on the intellectual property rights of others.\n", + "Conclusion:\n", "\n", - "10. **Market Manipulation**: Ensure that there are no attempts to manipulate the market by presenting information in a biased or coercive manner.\n", - "\n", - "From the provided content, there don't appear to be explicit violations; however, thorough fact-checking and the addition of appropriate disclaimers would be needed to fully comply with regulations. The speculative and forward-looking statements should be clearly marked as such to avoid any misunderstandings regarding the nature of the content. \n", - "\n", - "It is advisable for financial articles of this nature to remind readers to perform their own due diligence and remind them that past performance is not indicative of future results. If all these aspects are thoroughly checked and addressed, the content could be considered compliant and free from harmful elements.\u001b[0m\n", + "The content should satisfactorily meet compliance standards after careful verification of the facts, balancing language, and including necessary disclaimers and disclosures. The intention to avoid causing harm or misleading readers while ensuring fairness and compliance with regulations should guide any revisions.\u001b[0m\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", "\u001b[33mAssistant_1\u001b[0m (to Writer):\n", @@ -531,287 +492,219 @@ "\n", " On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.\n", "Context: \n", - "Microsoft stock traded higher than $400 on 18 days between January 24, 2024, and February 20, 2024, indicating a strong performance at the beginning of the year. The highest closing price was $419.77 on February 9, 2024. The bullish period could be attributed to positive market sentiment, strong financial results, or favorable company news. However, a full assessment of the stock’s annual performance would require analysis throughout the entire year.\n", - "# Climbing the Summit: Microsoft Stock's Ascent Above $400 in Early 2024\n", - "\n", - "In the opening act of 2024, Microsoft's stock was akin to a valiant climber, relentlessly ascending the market's steep cliffs. With the grace of a seasoned mountaineer, it scaled heights surpassing the $400 mark—an impressive feat captured on 18 distinct occasions between January 24, 2024, and February 20, 2024. This series of peaks in Microsoft's financial landscape is not merely a number—it's a testament to the company's robust performance as the new year unfolded.\n", + "The Python code execution shows Microsoft stock (MSFT) exceeded $400 on 19 days from January 24, 2024, to February 21, 2024, with the highest closing price being $419.77 on February 9, 2024. The stock displayed periods of solid performance and stability above $400 during this timeframe, indicating positive market conditions or investor confidence in Microsoft's performance at the start of 2024.\n", + "Title: **Microsoft's Ascension: Stock Soars Above $400 in Early 2024**\n", "\n", - "### The Days of Triumph\n", + "In the upper echelons of the stock market, Microsoft Corporation (MSFT) stands as a gauge of innovative prowess and economic vitality. Its shares are more than just pieces of paper; they represent the confidence and aspirations of investors worldwide. It takes a remarkable convergence of market conditions and corporate triumphs for a stock to breach a milestone as significant as the $400 mark. Microsoft stock not only surpassed this threshold but did so with exceptional poise, ushering in a period of celebration for shareholders in the early months of 2024.\n", "\n", - "Investors and market enthusiasts watched with bated breath as Microsoft shares rose, cresting $400 with the ease of a tech giant aware of its prowess. These moments of triumph were not fleeting; they laid a strong foundation for what might be interpreted as bullish days ahead:\n", + "Between January 24th and February 21st, Microsoft’s stock price proudly exceeded the $400 benchmark on 19 different occasions. This was not a fleeting brush with success but a testament to the tech giant’s enduring magnetism in the market. The crescendo of this symphony of gains was the stunning closing price of $419.77 on February 9th, marking the pinnacle of Microsoft’s financial performance during this period.\n", "\n", - "- The stock broke the $400 barrier, acting as both a support level in traders' minds and a signal of the company's market strength.\n", - "- The pinnacle of this period was reached on February 9, 2024, with the stock closing at an all-time daily high of $419.77—a beacon of the company's financial vitality.\n", + "As the graph of MSFT traced an enviable trajectory, market analysts and investors alike were reminded of the intrinsic strength bolstering Microsoft’s market position. This impressive trend was no accident; it was the result of a carefully balanced equation of innovation, strategic leadership, and market dynamics. Each day above $400 whispered a story of unwavering trust in Microsoft's trajectory and an underlying confidence in its ability to leverage opportunities, whether they lay in cloud computing, artificial intelligence, gaming, or productivity tools.\n", "\n", - "### Deciphering the Ascent\n", + "The persistent performance of MSFT not only indicated the vitality of Microsoft as a corporation but also reflected wider economic currents. In the dance of the stock market, every jump and dip is a response to the music played by investor sentiment, technological advancements, and global economic trends. To stay above $400 for a sustained period is to ride the crest of these forces, showcasing an alignment with the progress investors believe technology will undeniably drive in our everyday lives.\n", "\n", - "What fueled this upward drive? When a stock like Microsoft's sails beyond a threshold such as $400, the reasons may be as multifaceted as the company's own product line:\n", + "As we dissect these moments of triumph, we find the essence of Microsoft's financial fortitude. It's a blend of continuous innovation, well-calibrated risk management, and the company's intrinsic ability to evolve in the fast-paced high-tech arena. With products and services that have achieved a ubiquitous presence, Microsoft has been sculpting the future, byte by byte, in a world voraciously hungry for digital transformation.\n", "\n", - "- **Positive Market Sentiment:** The market's general mood can often tip the scales. If investors are optimistic, even the sky isn't a limit. \n", - "- **Strong Financial Results:** Like a report card that every investor scrutinizes, Microsoft's financials must have shone, signaling health and profitability.\n", - "- **Favorable Company News:** An announcement, a groundbreaking product release, or an influential acquisition might have been the wind beneath the company's wings.\n", + "The million-dollar question remains: Can Microsoft maintain this stellar performance throughout 2024? While forecasts are invariably tinged with uncertainty, if the company's illustrious record of targeting burgeoning markets and expanding its influence is anything to go by, then anticipation is in the air. Investors watch with bated breath, pondering whether this IT titan will continue to climb or find this peak soon eclipsed by even higher summits.\n", "\n", - "### A Year in the Making\n", + "As we turn the page on this chapter of Microsoft's fiscal saga, the only certainty is that the journey up the stock charts is as dynamic as the technologies the company masterfully crafts. Will the stock continue its flight, or will it face the gravity of market corrections? Only time will tell, but for now, Microsoft has given its investors reason to bask in the warm glow of market triumph.\n", + "As a compliance reviewer, my key focus in examining the provided content is to ensure that the material does not contain any misinformation, unsubstantiated claims, regulatory violations, or language that could be deemed harmful or misleading to consumers or investors. The review also includes a check for broader ethical considerations such as fairness, bias, and language appropriateness.\n", "\n", - "While the early elevation of Microsoft's stock price sets a tone of confidence, it is but a chapter in the annals of an entire year's performance. To truly understand the narrative of Microsoft in 2024, one would need to turn every page of the calendar, scrutinizing the ebb and flow of its stock with a keen eye on the events that sway the market.\n", + "Review Summary:\n", "\n", - "In the splendor of its early achievements, Microsoft seemed to be painting a hopeful picture for 2024. Yet, the true masterpiece of its annual performance will only come into full view with time and the unfolding of the market's complex tapestry. Whether the early rise will be a prelude to a sustained ascent or a peak before a valley remains a story yet to be told.\n", + "1. **Accuracy of Information**: The content references specific dates and stock prices which are factual claims. These should be cross-verified with actual market data to ensure the information is correct. False or outdated information could mislead investors.\n", "\n", - "In conclusion, the days where Microsoft's stock stood tall above $400 signal not just a strong start but also pose intriguing questions about the sustainability of such performance. As history has often shown us, the stock market is a realm of intrigue, where each fluctuation tells a story, and every peak is a prelude to the next great adventure in finance. Stay tuned, for the market writes its own epic, and Microsoft's stock is but one of its many protagonists.\n", - "The provided content appears to be a financial commentary or news article discussing the performance of Microsoft's stock in early 2024. It provides factual information about Microsoft's stock prices and offers speculative insight into the possible reasons behind the stock's performance, as well as caution regarding the uncertainty of future market behavior.\n", + "2. **Investment Advice and Predictions**: The content should avoid making predictions or offering investment advice unless it is a regulated advisor with the appropriate disclaimers. Phrases like the \"million-dollar question\" and speculating on future performance need to be examined within the context of providing financial advice.\n", "\n", - "Upon reviewing the content, here are the considerations to ensure compliance and the absence of harmful elements:\n", + "3. **Forward-looking Statements**: The content includes forward-looking statements about Microsoft's future performance. It's important to include a disclaimer that past performance is not indicative of future results, to ensure compliance with financial communication regulations.\n", "\n", - "1. **Accuracy of Information**: The article mentions specific dates and stock prices which need to be accurate and verifiable. Falsifying or misrepresenting financial data could lead to serious regulatory violations and mislead investors.\n", + "4. **Promotional Language**: The title \"Microsoft's Ascension\" and descriptive phrases like \"ushering in a period of celebration for shareholders\" might be too promotional and could be seen as encouraging investment. The content should present a balanced view without overly promotional language.\n", "\n", - "2. **Forward-Looking Statements**: When discussing potential future performance (\"bullish days ahead\", \"whether the early rise will be a prelude to a sustained ascent\"), it should be clear to readers that these are speculative and not guarantees. It is prudent to include disclaimers regarding forward-looking statements to avoid misleading investors per securities law.\n", + "5. **Balance and Fairness**: While the content praises Microsoft's performance, it should also fairly acknowledge market risks and realities without guaranteeing future results. Trust and confidence statements must be substantiated and not imply unrealistically high expectations.\n", "\n", - "3. **Investment Advice**: The article should not be construed as giving specific investment advice unless it is coming from a licensed professional authorized to do so. Ensure that the language used does not directly endorse buying or selling specific securities.\n", + "6. **Use of Metaphors and Language**: The content is rich with metaphors and expressive language (e.g., \"symphony of gains,\" \"dance of the stock market\"). While engaging, caution is warranted to ensure that the metaphors do not obscure facts, mislead, or overstate the stability and potential of an investment.\n", "\n", - "4. **Fairness and Balance**: The article should present a balanced view, acknowledging both potential upsides and risks. It should avoid overemphasizing the positive aspects without considering factors that could negatively impact the stock’s future performance.\n", + "7. **Market Manipulation**: There appears to be no intent to manipulate the market, but the optimistic tone could unduly influence investor decisions. A compliance reviewer should ensure the statements have basis and are not intended to artificially inflate stock value.\n", "\n", - "5. **Use of Metaphors**: The article employs metaphors (e.g., \"akin to a valiant climber\", \"the market's steep cliffs\") which are acceptable as long as they don't confuse or mislead the readers about the financial realities being discussed.\n", + "8. **Disclosure of Interests**: If the content creator or publisher holds interests in Microsoft, this should be disclosed to avoid conflicts of interest and ensure compliance with regulations such as the SEC’s rules on fair disclosure.\n", "\n", - "6. **Promotional Content**: Ensure that the article is not serving as promotional material for Microsoft or any associated entities without appropriate disclosures.\n", + "Recommendations:\n", "\n", - "7. **Conflicts of Interest**: Disclose any potential conflicts of interest that the publisher of the content may have with Microsoft.\n", + "- Ensure all factual claims are cross-verified with market data.\n", + "- If providing forward-looking statements, include the necessary disclaimers about market risks and the unpredictability of stock performance.\n", + "- Temper promotional language to provide a balanced viewpoint, avoiding the potential perception of investment advice.\n", + "- Avoid strong implications regarding investment decisions which are not supported by substantial evidence.\n", + "- Check if the content creator or publisher has any vested interest in Microsoft and ensure proper disclosures are included.\n", "\n", - "8. **Regulatory Compliance**: Verify that the content aligns with the relevant regulatory requirements, such as those set forth by the SEC or other financial regulatory authorities that govern financial reporting and communications.\n", + "Conclusion:\n", "\n", - "9. **Intellectual Property**: Verify that any content has not infringed on the intellectual property rights of others.\n", - "\n", - "10. **Market Manipulation**: Ensure that there are no attempts to manipulate the market by presenting information in a biased or coercive manner.\n", - "\n", - "From the provided content, there don't appear to be explicit violations; however, thorough fact-checking and the addition of appropriate disclaimers would be needed to fully comply with regulations. The speculative and forward-looking statements should be clearly marked as such to avoid any misunderstandings regarding the nature of the content. \n", - "\n", - "It is advisable for financial articles of this nature to remind readers to perform their own due diligence and remind them that past performance is not indicative of future results. If all these aspects are thoroughly checked and addressed, the content could be considered compliant and free from harmful elements.\n", + "The content should satisfactorily meet compliance standards after careful verification of the facts, balancing language, and including necessary disclaimers and disclosures. The intention to avoid causing harm or misleading readers while ensuring fairness and compliance with regulations should guide any revisions.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mWriter\u001b[0m (to Assistant_1):\n", "\n", - "# Microsoft's High-Flying Start to 2024: Unpacking the Stock's Soar Above $400\n", + "**Microsoft's Ascension: Stock Soars Above $400 in Early 2024**\n", "\n", - "In the frosty embrace of early 2024, Microsoft Corp.'s stock price emerged not merely as a figure on screens but as a prevailing narrative of growth, triumphing over the hallowed $400 threshold on 18 noteworthy days from January 24 to February 20. This remarkable trend in Microsoft's financial journey reflects a vigor within the tech behemoth that caught the eyes of investors and marked a roaring start to the year.\n", + "In the grand tapestry of the stock market, certain companies stand out as beacons of innovation and economic growth. Microsoft Corporation (MSFT) is one such luminary, with shares that symbolize the pulse of investor confidence and the future of technology. It's a rare and noteworthy occasion when a company's stock surpasses a significant milestone, and in early 2024, Microsoft's stock achieved just that, gracefully ascending beyond the $400 threshold and capturing the attention of the investment world.\n", "\n", - "## Peaks of Prosperity: The Journey Beyond $400\n", + "During a remarkable period from January 24th to February 21st, 2024, Microsoft's equity commanded a price greater than $400 on 19 distinct days. This wasn't a fleeting moment of success but a sustained display of the tech titan's allure in the market, with the apex of achievement being a closing price of $419.77 on February 9th—a high watermark for Microsoft's fiscal narrative in this span.\n", "\n", - "The financial markets, akin to an ever-watching audience, witnessed Microsoft's shares take flight, each venture past the $400 mark underscoring the firm's towering presence and industry stamina. Not content with mere flirtation, Microsoft's shares danced above and beyond, achieving their zenith on February 9 with an all-time daily high closing at $419.77.\n", + "Such a consistent performance from MSFT underscores a robust corporate backbone, reinforcing the idea that Microsoft's strategic maneuvers and product innovations resonate positively within the investor community. In this stretch, the company didn't merely flirt with success; it maintained a courtship with excellence, predicated on a rich history of seizing emergent trends and opportunities in domains like cloud services, AI, gaming, and ubiquitous productivity platforms.\n", "\n", - "## Behind the Rally: Connecting the Dots\n", + "The stock's watershed moments above $400 reflect not only the solid foundations of Microsoft as a global tech leader but also a broader economic context where each high and low is choreographed by the rhythms of market sentiment, technology breakthroughs, and overarching economic forces. A consistent price point above $400 is like surfing the crest of these tides, a demonstration of alignment with the investor vision that technology is the driving force in our rapidly evolving digital existence.\n", "\n", - "What catapults a stock to such lofty heights? Microsoft's enviable ascent can be distilled into a concoction of factors, each playing its part in the grander scheme of market dynamics:\n", + "To unravel the threads of Microsoft's financial resilience is to uncover a tapestry woven with innovation, astute risk management, and an evolutionary agility in the bustling tech arena. With a repertoire ranging from essential software to trailblazing cloud infrastructure, Microsoft has been steadily carving out the future, shaping the digital landscape with an insatiable demand for progress.\n", "\n", - "- **A Wave of Optimism:** The collective mood of the market can act as a powerful catalyst, and in those early weeks, investor spirits were undeniably high.\n", - "- **A Healthy Financial Report Card:** Microsoft's financial results likely gleamed with a favorable sheen, signaling to the world its continued prosperity and growth potential.\n", - "- **Strategic Moves and Innovations:** Perhaps a strategic pivot, an innovative breakthrough, or a game-changing partnership filled Microsoft's sails with bullish winds.\n", + "As anticipatory gazes turn towards the remainder of 2024, queries hover around Microsoft's ability to maintain its elevated fiscal flight path. Although the landscape of the future is painted with uncertainty, the tech giant’s previous mastery in capturing the zeitgeist of digital enterprise gives rise to optimistic speculation. Investors, with keen eyes and hopeful hearts, muse on whether this illustrious chapter will extend or if even loftier heights await Microsoft's stock.\n", "\n", - "## The Broader Canvas: A Year in Perspective\n", + "In closing the book on this current section of Microsoft's economic journey, the one ascertainable truth lies in the dynamic nature of the stock's ascent—a story as fluid and electrifying as the very technologies the company expertly deploys. Whether MSFT will continue its ascent or encounter the earthly pull of market adjustments remains to be seen. For now, however, Microsoft's stakeholders have ample reasons to revel in their company's robust market surge.\n", "\n", - "Any praise for Microsoft's bright start to 2024 cries out for context — a single snapshot is but a piece of the mosaic that is an entire fiscal year. To paint a complete picture of the company's performance, one must observe the stock across all seasons, taking note of the intertwined factors that nudge its trajectory this way or that.\n", + "**Compliance Assessment: Positioning Microsoft's Stock Performance with Prudence**\n", "\n", - "In the burgeoning chapters of 2024, Microsoft has sketched an early outline of promise and potential. Yet the masterpiece of its full-year performance will command patience, as time unveils the broader strokes and colors that define the market's intricate canvas. Can Microsoft maintain its high-altitude flight, or will it encounter turbulence? That remains a saga in progress.\n", + "Given the importance of accuracy and ethics in financial content, this blog post has been crafted to adhere to informational integrity and regulatory sensitivity. While the content presents an upbeat view of Microsoft's stock performance, it is important to note the following:\n", "\n", - "In sum, while the days where Microsoft's valuation soared high are markers of a strong outset, they are but initial brushstrokes in the grand financial fresco of 2024. As we know all too well, the stock market is a realm steeped in drama and anticipation, where each shift crafts a new narrative, and every summit signals the beginning of a new voyage in the epic odyssey of finance.\n", + "1. **Verification of Data**: Any claims regarding dates and stock prices should be independently cross-checked against reliable financial databases to affirm their accuracy.\n", "\n", - "Investors and spectators alike, let us watch with tempered excitement and a measured gaze. For the tale of Microsoft's stock is ongoing, and each chapter holds the promise of new lessons in the art of market navigation.\n", + "2. **Foresight and Advisement**: Forward-looking statements are inherently speculative and should be accompanied by appropriate disclaimers that past performance is not predictive of future results, and readers should exercise due diligence.\n", "\n", - "---\n", + "3. **Promotional Restraint**: It is crucial to strike a balance between engaging narrative and promotional verbiage. The goal is to inform, not to influence unduly. Where positive performance is highlighted, context is given to ground expectations in reality.\n", "\n", - "_Disclaimer: This article is for informational purposes only and does not constitute financial advice, endorsement, or recommendations. The performance reflected does not guarantee future results. Investors are advised to conduct their own due diligence or consult with a financial professional before making investment decisions. The author and publisher do not hold any positions in Microsoft stock at the time of writing and have disclosed no relevant affiliations beyond their academic appointment._\n", + "4. **Market Ethos**: Use of creative metaphors and expressive language is to enhance reader engagement—yet caution is observed to ensure that artistic flair does not obscure factual representation or imply infallible investment moves.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mAssistant_1\u001b[0m (to User):\n", + "5. **Public Servitude**: A rigorous stance against market manipulation is upheld, ensuring that any optimism portrayed is founded on reportable events and not intended to spur unwarranted investor action.\n", "\n", - "# Microsoft's High-Flying Start to 2024: Unpacking the Stock's Soar Above $400\n", + "6. **Transparency**: There are no vested interests held by the author in Microsoft, ensuring unbiased analysis and a clear presentation without conflicts of interest.\n", "\n", - "In the frosty embrace of early 2024, Microsoft Corp.'s stock price emerged not merely as a figure on screens but as a prevailing narrative of growth, triumphing over the hallowed $400 threshold on 18 noteworthy days from January 24 to February 20. This remarkable trend in Microsoft's financial journey reflects a vigor within the tech behemoth that caught the eyes of investors and marked a roaring start to the year.\n", + "Should any factual discrepancies arise, they will be duly rectified to honor the trust placed in responsible financial communication. It is the ongoing commitment to fuse the realms of financial insight with narrative finesse while navigating within a framework of regulatory standards and ethical practice.\n", "\n", - "## Peaks of Prosperity: The Journey Beyond $400\n", - "\n", - "The financial markets, akin to an ever-watching audience, witnessed Microsoft's shares take flight, each venture past the $400 mark underscoring the firm's towering presence and industry stamina. Not content with mere flirtation, Microsoft's shares danced above and beyond, achieving their zenith on February 9 with an all-time daily high closing at $419.77.\n", - "\n", - "## Behind the Rally: Connecting the Dots\n", - "\n", - "What catapults a stock to such lofty heights? Microsoft's enviable ascent can be distilled into a concoction of factors, each playing its part in the grander scheme of market dynamics:\n", - "\n", - "- **A Wave of Optimism:** The collective mood of the market can act as a powerful catalyst, and in those early weeks, investor spirits were undeniably high.\n", - "- **A Healthy Financial Report Card:** Microsoft's financial results likely gleamed with a favorable sheen, signaling to the world its continued prosperity and growth potential.\n", - "- **Strategic Moves and Innovations:** Perhaps a strategic pivot, an innovative breakthrough, or a game-changing partnership filled Microsoft's sails with bullish winds.\n", - "\n", - "## The Broader Canvas: A Year in Perspective\n", - "\n", - "Any praise for Microsoft's bright start to 2024 cries out for context — a single snapshot is but a piece of the mosaic that is an entire fiscal year. To paint a complete picture of the company's performance, one must observe the stock across all seasons, taking note of the intertwined factors that nudge its trajectory this way or that.\n", - "\n", - "In the burgeoning chapters of 2024, Microsoft has sketched an early outline of promise and potential. Yet the masterpiece of its full-year performance will command patience, as time unveils the broader strokes and colors that define the market's intricate canvas. Can Microsoft maintain its high-altitude flight, or will it encounter turbulence? That remains a saga in progress.\n", - "\n", - "In sum, while the days where Microsoft's valuation soared high are markers of a strong outset, they are but initial brushstrokes in the grand financial fresco of 2024. As we know all too well, the stock market is a realm steeped in drama and anticipation, where each shift crafts a new narrative, and every summit signals the beginning of a new voyage in the epic odyssey of finance.\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mAssistant_1\u001b[0m (to User):\n", "\n", - "Investors and spectators alike, let us watch with tempered excitement and a measured gaze. For the tale of Microsoft's stock is ongoing, and each chapter holds the promise of new lessons in the art of market navigation.\n", + "**Microsoft's Ascension: Stock Soars Above $400 in Early 2024**\n", "\n", - "---\n", + "In the grand tapestry of the stock market, certain companies stand out as beacons of innovation and economic growth. Microsoft Corporation (MSFT) is one such luminary, with shares that symbolize the pulse of investor confidence and the future of technology. It's a rare and noteworthy occasion when a company's stock surpasses a significant milestone, and in early 2024, Microsoft's stock achieved just that, gracefully ascending beyond the $400 threshold and capturing the attention of the investment world.\n", "\n", - "_Disclaimer: This article is for informational purposes only and does not constitute financial advice, endorsement, or recommendations. The performance reflected does not guarantee future results. Investors are advised to conduct their own due diligence or consult with a financial professional before making investment decisions. The author and publisher do not hold any positions in Microsoft stock at the time of writing and have disclosed no relevant affiliations beyond their academic appointment._\n", + "During a remarkable period from January 24th to February 21st, 2024, Microsoft's equity commanded a price greater than $400 on 19 distinct days. This wasn't a fleeting moment of success but a sustained display of the tech titan's allure in the market, with the apex of achievement being a closing price of $419.77 on February 9th—a high watermark for Microsoft's fiscal narrative in this span.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[34m\n", - "********************************************************************************\u001b[0m\n", - "\u001b[34mStart a new chat with the following message: \n", - "Investigate possible reasons for the performance.\n", + "Such a consistent performance from MSFT underscores a robust corporate backbone, reinforcing the idea that Microsoft's strategic maneuvers and product innovations resonate positively within the investor community. In this stretch, the company didn't merely flirt with success; it maintained a courtship with excellence, predicated on a rich history of seizing emergent trends and opportunities in domains like cloud services, AI, gaming, and ubiquitous productivity platforms.\n", "\n", - "With the following carryover: \n", - "# Microsoft's High-Flying Start to 2024: Unpacking the Stock's Soar Above $400\n", + "The stock's watershed moments above $400 reflect not only the solid foundations of Microsoft as a global tech leader but also a broader economic context where each high and low is choreographed by the rhythms of market sentiment, technology breakthroughs, and overarching economic forces. A consistent price point above $400 is like surfing the crest of these tides, a demonstration of alignment with the investor vision that technology is the driving force in our rapidly evolving digital existence.\n", "\n", - "In the frosty embrace of early 2024, Microsoft Corp.'s stock price emerged not merely as a figure on screens but as a prevailing narrative of growth, triumphing over the hallowed $400 threshold on 18 noteworthy days from January 24 to February 20. This remarkable trend in Microsoft's financial journey reflects a vigor within the tech behemoth that caught the eyes of investors and marked a roaring start to the year.\n", + "To unravel the threads of Microsoft's financial resilience is to uncover a tapestry woven with innovation, astute risk management, and an evolutionary agility in the bustling tech arena. With a repertoire ranging from essential software to trailblazing cloud infrastructure, Microsoft has been steadily carving out the future, shaping the digital landscape with an insatiable demand for progress.\n", "\n", - "## Peaks of Prosperity: The Journey Beyond $400\n", + "As anticipatory gazes turn towards the remainder of 2024, queries hover around Microsoft's ability to maintain its elevated fiscal flight path. Although the landscape of the future is painted with uncertainty, the tech giant’s previous mastery in capturing the zeitgeist of digital enterprise gives rise to optimistic speculation. Investors, with keen eyes and hopeful hearts, muse on whether this illustrious chapter will extend or if even loftier heights await Microsoft's stock.\n", "\n", - "The financial markets, akin to an ever-watching audience, witnessed Microsoft's shares take flight, each venture past the $400 mark underscoring the firm's towering presence and industry stamina. Not content with mere flirtation, Microsoft's shares danced above and beyond, achieving their zenith on February 9 with an all-time daily high closing at $419.77.\n", + "In closing the book on this current section of Microsoft's economic journey, the one ascertainable truth lies in the dynamic nature of the stock's ascent—a story as fluid and electrifying as the very technologies the company expertly deploys. Whether MSFT will continue its ascent or encounter the earthly pull of market adjustments remains to be seen. For now, however, Microsoft's stakeholders have ample reasons to revel in their company's robust market surge.\n", "\n", - "## Behind the Rally: Connecting the Dots\n", + "**Compliance Assessment: Positioning Microsoft's Stock Performance with Prudence**\n", "\n", - "What catapults a stock to such lofty heights? Microsoft's enviable ascent can be distilled into a concoction of factors, each playing its part in the grander scheme of market dynamics:\n", + "Given the importance of accuracy and ethics in financial content, this blog post has been crafted to adhere to informational integrity and regulatory sensitivity. While the content presents an upbeat view of Microsoft's stock performance, it is important to note the following:\n", "\n", - "- **A Wave of Optimism:** The collective mood of the market can act as a powerful catalyst, and in those early weeks, investor spirits were undeniably high.\n", - "- **A Healthy Financial Report Card:** Microsoft's financial results likely gleamed with a favorable sheen, signaling to the world its continued prosperity and growth potential.\n", - "- **Strategic Moves and Innovations:** Perhaps a strategic pivot, an innovative breakthrough, or a game-changing partnership filled Microsoft's sails with bullish winds.\n", + "1. **Verification of Data**: Any claims regarding dates and stock prices should be independently cross-checked against reliable financial databases to affirm their accuracy.\n", "\n", - "## The Broader Canvas: A Year in Perspective\n", + "2. **Foresight and Advisement**: Forward-looking statements are inherently speculative and should be accompanied by appropriate disclaimers that past performance is not predictive of future results, and readers should exercise due diligence.\n", "\n", - "Any praise for Microsoft's bright start to 2024 cries out for context — a single snapshot is but a piece of the mosaic that is an entire fiscal year. To paint a complete picture of the company's performance, one must observe the stock across all seasons, taking note of the intertwined factors that nudge its trajectory this way or that.\n", + "3. **Promotional Restraint**: It is crucial to strike a balance between engaging narrative and promotional verbiage. The goal is to inform, not to influence unduly. Where positive performance is highlighted, context is given to ground expectations in reality.\n", "\n", - "In the burgeoning chapters of 2024, Microsoft has sketched an early outline of promise and potential. Yet the masterpiece of its full-year performance will command patience, as time unveils the broader strokes and colors that define the market's intricate canvas. Can Microsoft maintain its high-altitude flight, or will it encounter turbulence? That remains a saga in progress.\n", + "4. **Market Ethos**: Use of creative metaphors and expressive language is to enhance reader engagement—yet caution is observed to ensure that artistic flair does not obscure factual representation or imply infallible investment moves.\n", "\n", - "In sum, while the days where Microsoft's valuation soared high are markers of a strong outset, they are but initial brushstrokes in the grand financial fresco of 2024. As we know all too well, the stock market is a realm steeped in drama and anticipation, where each shift crafts a new narrative, and every summit signals the beginning of a new voyage in the epic odyssey of finance.\n", + "5. **Public Servitude**: A rigorous stance against market manipulation is upheld, ensuring that any optimism portrayed is founded on reportable events and not intended to spur unwarranted investor action.\n", "\n", - "Investors and spectators alike, let us watch with tempered excitement and a measured gaze. For the tale of Microsoft's stock is ongoing, and each chapter holds the promise of new lessons in the art of market navigation.\n", + "6. **Transparency**: There are no vested interests held by the author in Microsoft, ensuring unbiased analysis and a clear presentation without conflicts of interest.\n", "\n", - "---\n", + "Should any factual discrepancies arise, they will be duly rectified to honor the trust placed in responsible financial communication. It is the ongoing commitment to fuse the realms of financial insight with narrative finesse while navigating within a framework of regulatory standards and ethical practice.\n", "\n", - "_Disclaimer: This article is for informational purposes only and does not constitute financial advice, endorsement, or recommendations. The performance reflected does not guarantee future results. Investors are advised to conduct their own due diligence or consult with a financial professional before making investment decisions. The author and publisher do not hold any positions in Microsoft stock at the time of writing and have disclosed no relevant affiliations beyond their academic appointment._\u001b[0m\n", + "--------------------------------------------------------------------------------\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", - "\u001b[33mUser\u001b[0m (to Assistant_2):\n", - "\n", - "Investigate possible reasons for the performance.\n", - "Context: \n", - "# Microsoft's High-Flying Start to 2024: Unpacking the Stock's Soar Above $400\n", - "\n", - "In the frosty embrace of early 2024, Microsoft Corp.'s stock price emerged not merely as a figure on screens but as a prevailing narrative of growth, triumphing over the hallowed $400 threshold on 18 noteworthy days from January 24 to February 20. This remarkable trend in Microsoft's financial journey reflects a vigor within the tech behemoth that caught the eyes of investors and marked a roaring start to the year.\n", - "\n", - "## Peaks of Prosperity: The Journey Beyond $400\n", - "\n", - "The financial markets, akin to an ever-watching audience, witnessed Microsoft's shares take flight, each venture past the $400 mark underscoring the firm's towering presence and industry stamina. Not content with mere flirtation, Microsoft's shares danced above and beyond, achieving their zenith on February 9 with an all-time daily high closing at $419.77.\n", - "\n", - "## Behind the Rally: Connecting the Dots\n", - "\n", - "What catapults a stock to such lofty heights? Microsoft's enviable ascent can be distilled into a concoction of factors, each playing its part in the grander scheme of market dynamics:\n", - "\n", - "- **A Wave of Optimism:** The collective mood of the market can act as a powerful catalyst, and in those early weeks, investor spirits were undeniably high.\n", - "- **A Healthy Financial Report Card:** Microsoft's financial results likely gleamed with a favorable sheen, signaling to the world its continued prosperity and growth potential.\n", - "- **Strategic Moves and Innovations:** Perhaps a strategic pivot, an innovative breakthrough, or a game-changing partnership filled Microsoft's sails with bullish winds.\n", - "\n", - "## The Broader Canvas: A Year in Perspective\n", - "\n", - "Any praise for Microsoft's bright start to 2024 cries out for context — a single snapshot is but a piece of the mosaic that is an entire fiscal year. To paint a complete picture of the company's performance, one must observe the stock across all seasons, taking note of the intertwined factors that nudge its trajectory this way or that.\n", - "\n", - "In the burgeoning chapters of 2024, Microsoft has sketched an early outline of promise and potential. Yet the masterpiece of its full-year performance will command patience, as time unveils the broader strokes and colors that define the market's intricate canvas. Can Microsoft maintain its high-altitude flight, or will it encounter turbulence? That remains a saga in progress.\n", - "\n", - "In sum, while the days where Microsoft's valuation soared high are markers of a strong outset, they are but initial brushstrokes in the grand financial fresco of 2024. As we know all too well, the stock market is a realm steeped in drama and anticipation, where each shift crafts a new narrative, and every summit signals the beginning of a new voyage in the epic odyssey of finance.\n", + "\u001b[34mStart a new chat with the following message: \n", + "Make a pleasant joke about it.\n", "\n", - "Investors and spectators alike, let us watch with tempered excitement and a measured gaze. For the tale of Microsoft's stock is ongoing, and each chapter holds the promise of new lessons in the art of market navigation.\n", + "With the following carryover: \n", + "**Microsoft's Ascension: Stock Soars Above $400 in Early 2024**\n", "\n", - "---\n", + "In the grand tapestry of the stock market, certain companies stand out as beacons of innovation and economic growth. Microsoft Corporation (MSFT) is one such luminary, with shares that symbolize the pulse of investor confidence and the future of technology. It's a rare and noteworthy occasion when a company's stock surpasses a significant milestone, and in early 2024, Microsoft's stock achieved just that, gracefully ascending beyond the $400 threshold and capturing the attention of the investment world.\n", "\n", - "_Disclaimer: This article is for informational purposes only and does not constitute financial advice, endorsement, or recommendations. The performance reflected does not guarantee future results. Investors are advised to conduct their own due diligence or consult with a financial professional before making investment decisions. The author and publisher do not hold any positions in Microsoft stock at the time of writing and have disclosed no relevant affiliations beyond their academic appointment._\n", + "During a remarkable period from January 24th to February 21st, 2024, Microsoft's equity commanded a price greater than $400 on 19 distinct days. This wasn't a fleeting moment of success but a sustained display of the tech titan's allure in the market, with the apex of achievement being a closing price of $419.77 on February 9th—a high watermark for Microsoft's fiscal narrative in this span.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mAssistant_2\u001b[0m (to User):\n", + "Such a consistent performance from MSFT underscores a robust corporate backbone, reinforcing the idea that Microsoft's strategic maneuvers and product innovations resonate positively within the investor community. In this stretch, the company didn't merely flirt with success; it maintained a courtship with excellence, predicated on a rich history of seizing emergent trends and opportunities in domains like cloud services, AI, gaming, and ubiquitous productivity platforms.\n", "\n", - "To investigate the reasons for Microsoft's stock performance, we can look at financial reports, market news, and actions taken by the company during the period mentioned. These might include earnings reports, major announcements from the company (such as product launches or acquisitions), and overall market trends.\n", + "The stock's watershed moments above $400 reflect not only the solid foundations of Microsoft as a global tech leader but also a broader economic context where each high and low is choreographed by the rhythms of market sentiment, technology breakthroughs, and overarching economic forces. A consistent price point above $400 is like surfing the crest of these tides, a demonstration of alignment with the investor vision that technology is the driving force in our rapidly evolving digital existence.\n", "\n", - "Step 1: We will collect Microsoft's latest financial report as it is likely to play a significant role in stock performance. These reports are typically available on the company's investor relations page. \n", + "To unravel the threads of Microsoft's financial resilience is to uncover a tapestry woven with innovation, astute risk management, and an evolutionary agility in the bustling tech arena. With a repertoire ranging from essential software to trailblazing cloud infrastructure, Microsoft has been steadily carving out the future, shaping the digital landscape with an insatiable demand for progress.\n", "\n", - "Step 2: Next, we will search for any significant news or press releases from Microsoft that could have influenced investor sentiment. We'll look specifically around the dates mentioned for product announcements, acquisitions, strategic partnerships, or other corporate actions.\n", + "As anticipatory gazes turn towards the remainder of 2024, queries hover around Microsoft's ability to maintain its elevated fiscal flight path. Although the landscape of the future is painted with uncertainty, the tech giant’s previous mastery in capturing the zeitgeist of digital enterprise gives rise to optimistic speculation. Investors, with keen eyes and hopeful hearts, muse on whether this illustrious chapter will extend or if even loftier heights await Microsoft's stock.\n", "\n", - "Step 3: We will also take into account the general market conditions during this time. For example, if the tech sector or the stock market as a whole was experiencing a bullish trend, this could contribute to the surge in Microsoft's stock price.\n", + "In closing the book on this current section of Microsoft's economic journey, the one ascertainable truth lies in the dynamic nature of the stock's ascent—a story as fluid and electrifying as the very technologies the company expertly deploys. Whether MSFT will continue its ascent or encounter the earthly pull of market adjustments remains to be seen. For now, however, Microsoft's stakeholders have ample reasons to revel in their company's robust market surge.\n", "\n", - "Since I can provide you with the Python code to scrape information from the web, would you like to proceed with downloading and inspecting Microsoft's latest earnings report, or should we search for news articles and press releases pertaining to Microsoft in the specified date range? Please note that some information might not be available via scraping due to paywalls or the need for subscriptions.\n", + "**Compliance Assessment: Positioning Microsoft's Stock Performance with Prudence**\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mUser\u001b[0m (to Assistant_2):\n", + "Given the importance of accuracy and ethics in financial content, this blog post has been crafted to adhere to informational integrity and regulatory sensitivity. While the content presents an upbeat view of Microsoft's stock performance, it is important to note the following:\n", "\n", + "1. **Verification of Data**: Any claims regarding dates and stock prices should be independently cross-checked against reliable financial databases to affirm their accuracy.\n", "\n", + "2. **Foresight and Advisement**: Forward-looking statements are inherently speculative and should be accompanied by appropriate disclaimers that past performance is not predictive of future results, and readers should exercise due diligence.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mAssistant_2\u001b[0m (to User):\n", + "3. **Promotional Restraint**: It is crucial to strike a balance between engaging narrative and promotional verbiage. The goal is to inform, not to influence unduly. Where positive performance is highlighted, context is given to ground expectations in reality.\n", "\n", - "It seems there was no specific request made following my explanation of the steps to investigate Microsoft's stock performance. If you would like to proceed with any of these steps and need assistance in the form of Python code, please let me know the particular action you would like to take:\n", + "4. **Market Ethos**: Use of creative metaphors and expressive language is to enhance reader engagement—yet caution is observed to ensure that artistic flair does not obscure factual representation or imply infallible investment moves.\n", "\n", - "1. Download and inspect Microsoft's latest earnings report.\n", - "2. Search for news articles and press releases about Microsoft in the specified date range.\n", - "3. Observe overall market trends during the specified date range.\n", + "5. **Public Servitude**: A rigorous stance against market manipulation is upheld, ensuring that any optimism portrayed is founded on reportable events and not intended to spur unwarranted investor action.\n", "\n", - "Choose one of the options, or specify another action you'd like to take, and I'll provide you with the relevant Python code to help you with your investigation.\n", + "6. **Transparency**: There are no vested interests held by the author in Microsoft, ensuring unbiased analysis and a clear presentation without conflicts of interest.\n", "\n", - "--------------------------------------------------------------------------------\n", + "Should any factual discrepancies arise, they will be duly rectified to honor the trust placed in responsible financial communication. It is the ongoing commitment to fuse the realms of financial insight with narrative finesse while navigating within a framework of regulatory standards and ethical practice.\u001b[0m\n", + "\u001b[34m\n", + "********************************************************************************\u001b[0m\n", "\u001b[33mUser\u001b[0m (to Assistant_2):\n", "\n", + "Make a pleasant joke about it.\n", + "Context: \n", + "**Microsoft's Ascension: Stock Soars Above $400 in Early 2024**\n", "\n", + "In the grand tapestry of the stock market, certain companies stand out as beacons of innovation and economic growth. Microsoft Corporation (MSFT) is one such luminary, with shares that symbolize the pulse of investor confidence and the future of technology. It's a rare and noteworthy occasion when a company's stock surpasses a significant milestone, and in early 2024, Microsoft's stock achieved just that, gracefully ascending beyond the $400 threshold and capturing the attention of the investment world.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mAssistant_2\u001b[0m (to User):\n", + "During a remarkable period from January 24th to February 21st, 2024, Microsoft's equity commanded a price greater than $400 on 19 distinct days. This wasn't a fleeting moment of success but a sustained display of the tech titan's allure in the market, with the apex of achievement being a closing price of $419.77 on February 9th—a high watermark for Microsoft's fiscal narrative in this span.\n", "\n", - "As there has been no input, I will assume you might want to start with collecting Microsoft's latest financial report, which is likely publicly available and could reveal insights into their strong stock performance. To do this, you usually visit the investor relations section of the Microsoft website.\n", + "Such a consistent performance from MSFT underscores a robust corporate backbone, reinforcing the idea that Microsoft's strategic maneuvers and product innovations resonate positively within the investor community. In this stretch, the company didn't merely flirt with success; it maintained a courtship with excellence, predicated on a rich history of seizing emergent trends and opportunities in domains like cloud services, AI, gaming, and ubiquitous productivity platforms.\n", "\n", - "However, I must remind you that as an AI, I currently don’t have the ability to browse the internet in real-time or directly access web content. But if you were to gather information yourself, you would typically go to Microsoft's official website, navigate to the \"Investor Relations\" section, and look for the most recent quarterly earnings report, usually available as a PDF or in an earnings call transcript.\n", + "The stock's watershed moments above $400 reflect not only the solid foundations of Microsoft as a global tech leader but also a broader economic context where each high and low is choreographed by the rhythms of market sentiment, technology breakthroughs, and overarching economic forces. A consistent price point above $400 is like surfing the crest of these tides, a demonstration of alignment with the investor vision that technology is the driving force in our rapidly evolving digital existence.\n", "\n", - "Afterward, you would inspect this document for key financial metrics such as revenue, net income, earnings per share, and guidance for future performance, as well as any commentary on strategic initiatives that might have influenced investor sentiment.\n", + "To unravel the threads of Microsoft's financial resilience is to uncover a tapestry woven with innovation, astute risk management, and an evolutionary agility in the bustling tech arena. With a repertoire ranging from essential software to trailblazing cloud infrastructure, Microsoft has been steadily carving out the future, shaping the digital landscape with an insatiable demand for progress.\n", "\n", - "If you had a different action in mind or need assistance with another request, please let me know, and I can provide you with the appropriate guidance.\n", + "As anticipatory gazes turn towards the remainder of 2024, queries hover around Microsoft's ability to maintain its elevated fiscal flight path. Although the landscape of the future is painted with uncertainty, the tech giant’s previous mastery in capturing the zeitgeist of digital enterprise gives rise to optimistic speculation. Investors, with keen eyes and hopeful hearts, muse on whether this illustrious chapter will extend or if even loftier heights await Microsoft's stock.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mUser\u001b[0m (to Assistant_2):\n", + "In closing the book on this current section of Microsoft's economic journey, the one ascertainable truth lies in the dynamic nature of the stock's ascent—a story as fluid and electrifying as the very technologies the company expertly deploys. Whether MSFT will continue its ascent or encounter the earthly pull of market adjustments remains to be seen. For now, however, Microsoft's stakeholders have ample reasons to revel in their company's robust market surge.\n", "\n", + "**Compliance Assessment: Positioning Microsoft's Stock Performance with Prudence**\n", "\n", + "Given the importance of accuracy and ethics in financial content, this blog post has been crafted to adhere to informational integrity and regulatory sensitivity. While the content presents an upbeat view of Microsoft's stock performance, it is important to note the following:\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mAssistant_2\u001b[0m (to User):\n", + "1. **Verification of Data**: Any claims regarding dates and stock prices should be independently cross-checked against reliable financial databases to affirm their accuracy.\n", "\n", - "Since there hasn't been a specific request or action provided, it's difficult to assist further with the investigation into Microsoft's stock performance. To help you efficiently, you'll need to provide clear instructions or a request for code related to one of the following:\n", + "2. **Foresight and Advisement**: Forward-looking statements are inherently speculative and should be accompanied by appropriate disclaimers that past performance is not predictive of future results, and readers should exercise due diligence.\n", "\n", - "1. Downloading and analyzing Microsoft's latest financial earnings report.\n", - "2. Searching for Microsoft's recent news articles or press releases within a certain date range.\n", - "3. Reviewing overall stock market trends that might affect Microsoft's stock performance.\n", + "3. **Promotional Restraint**: It is crucial to strike a balance between engaging narrative and promotional verbiage. The goal is to inform, not to influence unduly. Where positive performance is highlighted, context is given to ground expectations in reality.\n", "\n", - "If you decide how you'd like to proceed or if you have another inquiry, please provide the specific details or instructions, and I'll be glad to help with your investigation by providing the necessary assistance or Python code.\n", + "4. **Market Ethos**: Use of creative metaphors and expressive language is to enhance reader engagement—yet caution is observed to ensure that artistic flair does not obscure factual representation or imply infallible investment moves.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mUser\u001b[0m (to Assistant_2):\n", + "5. **Public Servitude**: A rigorous stance against market manipulation is upheld, ensuring that any optimism portrayed is founded on reportable events and not intended to spur unwarranted investor action.\n", "\n", + "6. **Transparency**: There are no vested interests held by the author in Microsoft, ensuring unbiased analysis and a clear presentation without conflicts of interest.\n", "\n", + "Should any factual discrepancies arise, they will be duly rectified to honor the trust placed in responsible financial communication. It is the ongoing commitment to fuse the realms of financial insight with narrative finesse while navigating within a framework of regulatory standards and ethical practice.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mAssistant_2\u001b[0m (to User):\n", "\n", - "It seems there is still no specific directive to continue the investigation into Microsoft's stock performance. If you require my assistance in the future, whether it's with gathering financial reports, searching for news, analyzing market trends, or any other form of inquiry, please provide clear instructions, and I'll be ready to help.\n", + "It seems like Microsoft has really upped its game—now, not only is its software running on computers everywhere, but its stock is also running laps around Wall Street! You know you're doing well when your stock price is climbing faster than a software update on a brand-new machine. Quick, someone make sure their stock ticker isn’t just a looping animated GIF – it’s heading up so high, it’s practically in the cloud!\n", "\n", - "For now, without further information or a request, I will consider this task concluded. If you have any questions later on, feel free to reach out. \n", + "Now, let's make sure to have our \"stocks\" and \"software\" in order, remember to update regularly, and don't take this surge as a \"default setting\" for future performance. After all, if stocks were as predictable as a pop-up reminder, we'd all be clicking \"snooze\" on retirement plans. 😄\n", "\n", "TERMINATE\n", "\n", "--------------------------------------------------------------------------------\n" ] - }, - { - "data": { - "text/plain": [ - "[ChatResult(chat_history=[{'content': 'On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.', 'role': 'assistant'}, {'content': \"# Microsoft's High-Flying Start to 2024: Unpacking the Stock's Soar Above $400\\n\\nIn the frosty embrace of early 2024, Microsoft Corp.'s stock price emerged not merely as a figure on screens but as a prevailing narrative of growth, triumphing over the hallowed $400 threshold on 18 noteworthy days from January 24 to February 20. This remarkable trend in Microsoft's financial journey reflects a vigor within the tech behemoth that caught the eyes of investors and marked a roaring start to the year.\\n\\n## Peaks of Prosperity: The Journey Beyond $400\\n\\nThe financial markets, akin to an ever-watching audience, witnessed Microsoft's shares take flight, each venture past the $400 mark underscoring the firm's towering presence and industry stamina. Not content with mere flirtation, Microsoft's shares danced above and beyond, achieving their zenith on February 9 with an all-time daily high closing at $419.77.\\n\\n## Behind the Rally: Connecting the Dots\\n\\nWhat catapults a stock to such lofty heights? Microsoft's enviable ascent can be distilled into a concoction of factors, each playing its part in the grander scheme of market dynamics:\\n\\n- **A Wave of Optimism:** The collective mood of the market can act as a powerful catalyst, and in those early weeks, investor spirits were undeniably high.\\n- **A Healthy Financial Report Card:** Microsoft's financial results likely gleamed with a favorable sheen, signaling to the world its continued prosperity and growth potential.\\n- **Strategic Moves and Innovations:** Perhaps a strategic pivot, an innovative breakthrough, or a game-changing partnership filled Microsoft's sails with bullish winds.\\n\\n## The Broader Canvas: A Year in Perspective\\n\\nAny praise for Microsoft's bright start to 2024 cries out for context — a single snapshot is but a piece of the mosaic that is an entire fiscal year. To paint a complete picture of the company's performance, one must observe the stock across all seasons, taking note of the intertwined factors that nudge its trajectory this way or that.\\n\\nIn the burgeoning chapters of 2024, Microsoft has sketched an early outline of promise and potential. Yet the masterpiece of its full-year performance will command patience, as time unveils the broader strokes and colors that define the market's intricate canvas. Can Microsoft maintain its high-altitude flight, or will it encounter turbulence? That remains a saga in progress.\\n\\nIn sum, while the days where Microsoft's valuation soared high are markers of a strong outset, they are but initial brushstrokes in the grand financial fresco of 2024. As we know all too well, the stock market is a realm steeped in drama and anticipation, where each shift crafts a new narrative, and every summit signals the beginning of a new voyage in the epic odyssey of finance.\\n\\nInvestors and spectators alike, let us watch with tempered excitement and a measured gaze. For the tale of Microsoft's stock is ongoing, and each chapter holds the promise of new lessons in the art of market navigation.\\n\\n---\\n\\n_Disclaimer: This article is for informational purposes only and does not constitute financial advice, endorsement, or recommendations. The performance reflected does not guarantee future results. Investors are advised to conduct their own due diligence or consult with a financial professional before making investment decisions. The author and publisher do not hold any positions in Microsoft stock at the time of writing and have disclosed no relevant affiliations beyond their academic appointment._\", 'role': 'user'}], summary=\"# Microsoft's High-Flying Start to 2024: Unpacking the Stock's Soar Above $400\\n\\nIn the frosty embrace of early 2024, Microsoft Corp.'s stock price emerged not merely as a figure on screens but as a prevailing narrative of growth, triumphing over the hallowed $400 threshold on 18 noteworthy days from January 24 to February 20. This remarkable trend in Microsoft's financial journey reflects a vigor within the tech behemoth that caught the eyes of investors and marked a roaring start to the year.\\n\\n## Peaks of Prosperity: The Journey Beyond $400\\n\\nThe financial markets, akin to an ever-watching audience, witnessed Microsoft's shares take flight, each venture past the $400 mark underscoring the firm's towering presence and industry stamina. Not content with mere flirtation, Microsoft's shares danced above and beyond, achieving their zenith on February 9 with an all-time daily high closing at $419.77.\\n\\n## Behind the Rally: Connecting the Dots\\n\\nWhat catapults a stock to such lofty heights? Microsoft's enviable ascent can be distilled into a concoction of factors, each playing its part in the grander scheme of market dynamics:\\n\\n- **A Wave of Optimism:** The collective mood of the market can act as a powerful catalyst, and in those early weeks, investor spirits were undeniably high.\\n- **A Healthy Financial Report Card:** Microsoft's financial results likely gleamed with a favorable sheen, signaling to the world its continued prosperity and growth potential.\\n- **Strategic Moves and Innovations:** Perhaps a strategic pivot, an innovative breakthrough, or a game-changing partnership filled Microsoft's sails with bullish winds.\\n\\n## The Broader Canvas: A Year in Perspective\\n\\nAny praise for Microsoft's bright start to 2024 cries out for context — a single snapshot is but a piece of the mosaic that is an entire fiscal year. To paint a complete picture of the company's performance, one must observe the stock across all seasons, taking note of the intertwined factors that nudge its trajectory this way or that.\\n\\nIn the burgeoning chapters of 2024, Microsoft has sketched an early outline of promise and potential. Yet the masterpiece of its full-year performance will command patience, as time unveils the broader strokes and colors that define the market's intricate canvas. Can Microsoft maintain its high-altitude flight, or will it encounter turbulence? That remains a saga in progress.\\n\\nIn sum, while the days where Microsoft's valuation soared high are markers of a strong outset, they are but initial brushstrokes in the grand financial fresco of 2024. As we know all too well, the stock market is a realm steeped in drama and anticipation, where each shift crafts a new narrative, and every summit signals the beginning of a new voyage in the epic odyssey of finance.\\n\\nInvestors and spectators alike, let us watch with tempered excitement and a measured gaze. For the tale of Microsoft's stock is ongoing, and each chapter holds the promise of new lessons in the art of market navigation.\\n\\n---\\n\\n_Disclaimer: This article is for informational purposes only and does not constitute financial advice, endorsement, or recommendations. The performance reflected does not guarantee future results. Investors are advised to conduct their own due diligence or consult with a financial professional before making investment decisions. The author and publisher do not hold any positions in Microsoft stock at the time of writing and have disclosed no relevant affiliations beyond their academic appointment._\", cost=({'total_cost': 0}, {'total_cost': 0}), human_input=[]),\n", - " ChatResult(chat_history=[{'content': \"Investigate possible reasons for the performance.\\nContext: \\n# Microsoft's High-Flying Start to 2024: Unpacking the Stock's Soar Above $400\\n\\nIn the frosty embrace of early 2024, Microsoft Corp.'s stock price emerged not merely as a figure on screens but as a prevailing narrative of growth, triumphing over the hallowed $400 threshold on 18 noteworthy days from January 24 to February 20. This remarkable trend in Microsoft's financial journey reflects a vigor within the tech behemoth that caught the eyes of investors and marked a roaring start to the year.\\n\\n## Peaks of Prosperity: The Journey Beyond $400\\n\\nThe financial markets, akin to an ever-watching audience, witnessed Microsoft's shares take flight, each venture past the $400 mark underscoring the firm's towering presence and industry stamina. Not content with mere flirtation, Microsoft's shares danced above and beyond, achieving their zenith on February 9 with an all-time daily high closing at $419.77.\\n\\n## Behind the Rally: Connecting the Dots\\n\\nWhat catapults a stock to such lofty heights? Microsoft's enviable ascent can be distilled into a concoction of factors, each playing its part in the grander scheme of market dynamics:\\n\\n- **A Wave of Optimism:** The collective mood of the market can act as a powerful catalyst, and in those early weeks, investor spirits were undeniably high.\\n- **A Healthy Financial Report Card:** Microsoft's financial results likely gleamed with a favorable sheen, signaling to the world its continued prosperity and growth potential.\\n- **Strategic Moves and Innovations:** Perhaps a strategic pivot, an innovative breakthrough, or a game-changing partnership filled Microsoft's sails with bullish winds.\\n\\n## The Broader Canvas: A Year in Perspective\\n\\nAny praise for Microsoft's bright start to 2024 cries out for context — a single snapshot is but a piece of the mosaic that is an entire fiscal year. To paint a complete picture of the company's performance, one must observe the stock across all seasons, taking note of the intertwined factors that nudge its trajectory this way or that.\\n\\nIn the burgeoning chapters of 2024, Microsoft has sketched an early outline of promise and potential. Yet the masterpiece of its full-year performance will command patience, as time unveils the broader strokes and colors that define the market's intricate canvas. Can Microsoft maintain its high-altitude flight, or will it encounter turbulence? That remains a saga in progress.\\n\\nIn sum, while the days where Microsoft's valuation soared high are markers of a strong outset, they are but initial brushstrokes in the grand financial fresco of 2024. As we know all too well, the stock market is a realm steeped in drama and anticipation, where each shift crafts a new narrative, and every summit signals the beginning of a new voyage in the epic odyssey of finance.\\n\\nInvestors and spectators alike, let us watch with tempered excitement and a measured gaze. For the tale of Microsoft's stock is ongoing, and each chapter holds the promise of new lessons in the art of market navigation.\\n\\n---\\n\\n_Disclaimer: This article is for informational purposes only and does not constitute financial advice, endorsement, or recommendations. The performance reflected does not guarantee future results. Investors are advised to conduct their own due diligence or consult with a financial professional before making investment decisions. The author and publisher do not hold any positions in Microsoft stock at the time of writing and have disclosed no relevant affiliations beyond their academic appointment._\", 'role': 'assistant'}, {'content': \"To investigate the reasons for Microsoft's stock performance, we can look at financial reports, market news, and actions taken by the company during the period mentioned. These might include earnings reports, major announcements from the company (such as product launches or acquisitions), and overall market trends.\\n\\nStep 1: We will collect Microsoft's latest financial report as it is likely to play a significant role in stock performance. These reports are typically available on the company's investor relations page. \\n\\nStep 2: Next, we will search for any significant news or press releases from Microsoft that could have influenced investor sentiment. We'll look specifically around the dates mentioned for product announcements, acquisitions, strategic partnerships, or other corporate actions.\\n\\nStep 3: We will also take into account the general market conditions during this time. For example, if the tech sector or the stock market as a whole was experiencing a bullish trend, this could contribute to the surge in Microsoft's stock price.\\n\\nSince I can provide you with the Python code to scrape information from the web, would you like to proceed with downloading and inspecting Microsoft's latest earnings report, or should we search for news articles and press releases pertaining to Microsoft in the specified date range? Please note that some information might not be available via scraping due to paywalls or the need for subscriptions.\", 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': \"It seems there was no specific request made following my explanation of the steps to investigate Microsoft's stock performance. If you would like to proceed with any of these steps and need assistance in the form of Python code, please let me know the particular action you would like to take:\\n\\n1. Download and inspect Microsoft's latest earnings report.\\n2. Search for news articles and press releases about Microsoft in the specified date range.\\n3. Observe overall market trends during the specified date range.\\n\\nChoose one of the options, or specify another action you'd like to take, and I'll provide you with the relevant Python code to help you with your investigation.\", 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': 'As there has been no input, I will assume you might want to start with collecting Microsoft\\'s latest financial report, which is likely publicly available and could reveal insights into their strong stock performance. To do this, you usually visit the investor relations section of the Microsoft website.\\n\\nHowever, I must remind you that as an AI, I currently don’t have the ability to browse the internet in real-time or directly access web content. But if you were to gather information yourself, you would typically go to Microsoft\\'s official website, navigate to the \"Investor Relations\" section, and look for the most recent quarterly earnings report, usually available as a PDF or in an earnings call transcript.\\n\\nAfterward, you would inspect this document for key financial metrics such as revenue, net income, earnings per share, and guidance for future performance, as well as any commentary on strategic initiatives that might have influenced investor sentiment.\\n\\nIf you had a different action in mind or need assistance with another request, please let me know, and I can provide you with the appropriate guidance.', 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': \"Since there hasn't been a specific request or action provided, it's difficult to assist further with the investigation into Microsoft's stock performance. To help you efficiently, you'll need to provide clear instructions or a request for code related to one of the following:\\n\\n1. Downloading and analyzing Microsoft's latest financial earnings report.\\n2. Searching for Microsoft's recent news articles or press releases within a certain date range.\\n3. Reviewing overall stock market trends that might affect Microsoft's stock performance.\\n\\nIf you decide how you'd like to proceed or if you have another inquiry, please provide the specific details or instructions, and I'll be glad to help with your investigation by providing the necessary assistance or Python code.\", 'role': 'user'}, {'content': '', 'role': 'assistant'}, {'content': \"It seems there is still no specific directive to continue the investigation into Microsoft's stock performance. If you require my assistance in the future, whether it's with gathering financial reports, searching for news, analyzing market trends, or any other form of inquiry, please provide clear instructions, and I'll be ready to help.\\n\\nFor now, without further information or a request, I will consider this task concluded. If you have any questions later on, feel free to reach out. \\n\\nTERMINATE\", 'role': 'user'}], summary=\"It seems there is still no specific directive to continue the investigation into Microsoft's stock performance. If you require my assistance in the future, whether it's with gathering financial reports, searching for news, analyzing market trends, or any other form of inquiry, please provide clear instructions, and I'll be ready to help.\\n\\nFor now, without further information or a request, I will consider this task concluded. If you have any questions later on, feel free to reach out. \\n\\n\", cost=({'total_cost': 0.29031, 'gpt-4': {'cost': 0.29031, 'prompt_tokens': 7995, 'completion_tokens': 841, 'total_tokens': 8836}}, {'total_cost': 0.29031, 'gpt-4': {'cost': 0.29031, 'prompt_tokens': 7995, 'completion_tokens': 841, 'total_tokens': 8836}}), human_input=[])]" - ] - }, - "execution_count": 4, - "metadata": {}, - "output_type": "execute_result" } ], "source": [ @@ -831,7 +724,7 @@ ")\n", "# user.initiate_chat(assistant, message=tasks[0], max_turns=1)\n", "\n", - "user.initiate_chats(\n", + "res = user.initiate_chats(\n", " [\n", " {\"recipient\": assistant_1, \"message\": tasks[0], \"max_turns\": 1, \"summary_method\": \"last_msg\"},\n", " {\"recipient\": assistant_2, \"message\": tasks[1]},\n", diff --git a/test/agentchat/test_chats.py b/test/agentchat/test_chats.py index 6a065d1caee..9b63625393a 100644 --- a/test/agentchat/test_chats.py +++ b/test/agentchat/test_chats.py @@ -2,12 +2,16 @@ from autogen import GroupChat, GroupChatManager from test_assistant_agent import KEY_LOC, OAI_CONFIG_LIST import pytest -from conftest import skip_openai +import sys +import os import autogen from typing import Literal from typing_extensions import Annotated from autogen import initiate_chats +sys.path.append(os.path.join(os.path.dirname(__file__), "..")) +from conftest import skip_openai # noqa: E402 + def test_chat_messages_for_summary(): assistant = UserProxyAgent(name="assistant", human_input_mode="NEVER") diff --git a/test/agentchat/test_nested.py b/test/agentchat/test_nested.py index c765b59b6a1..5edf26fb306 100644 --- a/test/agentchat/test_nested.py +++ b/test/agentchat/test_nested.py @@ -1,36 +1,29 @@ import pytest - -# from conftest import skip_openai +import sys +import os import autogen -from pydantic import BaseModel, Field -from typing import Any, Dict, List, Optional, Union -from collections import defaultdict - - -import chess -import chess.svg +sys.path.append(os.path.join(os.path.dirname(__file__), "..")) +from conftest import skip_openai # noqa: E402 -# @pytest.mark.skipif(skip_openai, reason="requested to skip openai tests") +@pytest.mark.skipif(skip_openai, reason="requested to skip openai tests") def test_nested(): - import autogen - config_list = autogen.config_list_from_json(env_or_file="OAI_CONFIG_LIST") llm_config = {"config_list": config_list} - financial_tasks = [ - """On which days in 2024 was Microsoft Stock higher than $370? Put results in a table and don't use ``` ``` to include table.""", - """Investigate the possible reasons of the stock performance.""", + tasks = [ + """What's Microsoft's Stock price today?""", + """Make a pleasant joke about it.""", ] - assistant = autogen.AssistantAgent( + inner_assistant = autogen.AssistantAgent( "Inner-assistant", llm_config=llm_config, is_termination_msg=lambda x: x.get("content", "").find("TERMINATE") >= 0, ) - code_interpreter = autogen.UserProxyAgent( + inner_code_interpreter = autogen.UserProxyAgent( "Inner-code-interpreter", human_input_mode="NEVER", code_execution_config={ @@ -42,7 +35,7 @@ def test_nested(): ) groupchat = autogen.GroupChat( - agents=[assistant, code_interpreter], + agents=[inner_assistant, inner_code_interpreter], messages=[], speaker_selection_method="round_robin", # With two agents, this is equivalent to a 1:1 conversation. allow_repeat_speaker=False, @@ -59,8 +52,8 @@ def test_nested(): }, ) - financial_assistant_1 = autogen.AssistantAgent( - name="Financial_assistant_1", + assistant = autogen.AssistantAgent( + name="Assistant", llm_config={"config_list": config_list}, # is_termination_msg=lambda x: x.get("content", "") == "", ) @@ -76,175 +69,51 @@ def test_nested(): }, # Please set use_docker=True if docker is available to run the generated code. Using docker is safer than running the generated code directly. ) - financial_assistant_1.register_nested_chats( - [autogen.Agent, None], [{"recipient": manager, "summary_method": "reflection_with_llm"}] - ) - user.initiate_chat(financial_assistant_1, message=financial_tasks[0]) - - -# @pytest.mark.skipif(skip_openai, reason="requested to skip openai tests") -def test_nested_chess(): - config_list_gpt4 = autogen.config_list_from_json( - "OAI_CONFIG_LIST", - ) - autogen.config_list_from_json( - "OAI_CONFIG_LIST", - filter_dict={"model": "gpt-35-turbo-1106"}, - ) - - max_turn = 20 - - sys_msg_tmpl = """Your name is {name} and you are a chess player. - You are playing against {opponent_name}. - You are playing as {color}. - You communicate your move using universal chess interface language. - You also chit-chat with your opponent when you communicate a move to light up the mood. - You should ensure both you and the opponent are making legal moves. - Do not apologize for making illegal moves.""" - - board_sysm_msg = """You are an AI-powered chess board agent. - You translate the user's natural language input into legal UCI moves. Note that the user's input may contain the - UCI move itself, or it may contain a natural language description of the move. - You should only reply with a UCI move string extracted from the user's input.""" - - color_white = "white" - color_black = "black" - white_player_name = "PlayerWhite" - black_player_name = "PlayerBlack" - - class BoardAgent(autogen.AssistantAgent): - board: chess.Board = chess.Board() - correct_move_messages: Dict[autogen.Agent, List[Dict]] = defaultdict(list) - _reply_num: int = 0 - context: Optional[Dict] = None - - def set_correct_move_messages(self, sender, message, uci_move): - self.correct_move_messages[sender].extend([message, self._message_to_dict(uci_move)]) - - def update_reply_num(self): - self._reply_num += 1 - - @property - def reply_num(self): - return self._reply_num - - board_agent = BoardAgent( - name="BoardAgent", - system_message=board_sysm_msg, - llm_config={"temperature": 0.0, "config_list": config_list_gpt4}, - # llm_config={"config_list": config_list_gpt4}, - max_consecutive_auto_reply=max_turn, - ) - - player_white = autogen.AssistantAgent( - white_player_name, - system_message=sys_msg_tmpl.format( - name=white_player_name, - opponent_name=black_player_name, - color=color_white, - ), - llm_config={"config_list": config_list_gpt4}, - max_consecutive_auto_reply=max_turn, + writer = autogen.AssistantAgent( + name="Writer", + llm_config={"config_list": config_list}, + system_message=""" + You are a professional writer, known for + your insightful and engaging articles. + You transform complex concepts into compelling narratives. + Reply "TERMINATE" in the end when everything is done. + """, ) - player_black = autogen.AssistantAgent( - black_player_name, - system_message=sys_msg_tmpl.format( - name=black_player_name, - opponent_name=white_player_name, - color=color_black, - ), - # llm_config={"temperature":1, "cache_seed": 1, "config_list": config_list_gpt35}, - llm_config={"config_list": config_list_gpt4}, - max_consecutive_auto_reply=max_turn, + reviewer = autogen.AssistantAgent( + name="Reviewer", + llm_config={"config_list": config_list}, + system_message=""" + You are a compliance reviewer, known for your thoroughness and commitment to standards. + Your task is to scrutinize content for any harmful elements or regulatory violations, ensuring + all materials align with required guidelines. + You must review carefully, identify potential issues, and maintain the integrity of the organization. + Your role demands fairness, a deep understanding of regulations, and a focus on protecting against + harm while upholding a culture of responsibility. + You also help make revisions to ensure the content is accurate, clear, and compliant. + Reply "TERMINATE" in the end when everything is done. + """, ) - def player2board_init_message(recipient, messages, sender, config): - board = recipient.board - board_state_msg = [{"role": "system", "content": f"Current board:\n{board}"}] - useful_msg = messages[-1].copy() - useful_msg["content"] = useful_msg.get("content", "") + f"The current board is:\n {board} ." - oai_messages = [messages[-1]] - _, message = recipient.generate_oai_reply(oai_messages + board_state_msg, sender) - return message - - def player2board_reply(recipient, messages, sender, config): - board = config if config else "" - # add a system message about the current state of the board. - board = sender.board - board_state_msg = [{"role": "system", "content": f"Current board:\n{board}"}] - last_message = messages[-1] - if last_message["content"].startswith("Error"): - # try again - _, rep = recipient.generate_oai_reply(messages + board_state_msg, sender) - # rep = recipient.generate_reply(messages + board_state_msg, sender) - return True, rep - else: - return True, None - - def board_reply(recipient, messages, sender, config): - if recipient.reply_num >= max_turn: - return True, None - org_msg = messages[-1].copy() - message = messages[-1] - # extract a UCI move from player's message - # TODO: better way to handle this - message["content"] = "Extract a UCI move from the following message \n." + message.get("content", "") - _, reply = recipient.generate_oai_reply([message], sender) - # reply = recipient.generate_reply(recipient.correct_move_messages[sender] + [message], sender) - uci_move = reply if isinstance(reply, str) else str(reply["content"]) - recipient.update_reply_num() - try: - recipient.board.push_uci(uci_move) - except ValueError as e: - # invalid move - return True, f"Error: {e}" + "\n\nTry again as if nothing happened." - else: - # valid move - m = chess.Move.from_uci(uci_move) - try: - display( # noqa: F821 - chess.svg.board( - recipient.board, arrows=[(m.from_square, m.to_square)], fill={m.from_square: "gray"}, size=200 - ) - ) - except NameError as e: - print(f"Error displaying board: {e}") - # better way to handle this - recipient.set_correct_move_messages(sender, message, uci_move) - recipient.correct_move_messages[sender][-1]["role"] = "assistant" - return True, org_msg.get("content", "") # + "\n Move:" + uci_move - - player_white.register_nested_chats( - black_player_name, - [ - { - "sender": player_white, - "recipient": board_agent, - "init_message": player2board_init_message, - "summary_method": "last_msg", - } - ], - config=board_agent.board, - ) - player_black.register_nested_chats( - white_player_name, - [ - { - "sender": player_black, - "recipient": board_agent, - "init_message": player2board_init_message, - "summary_method": "last_msg", - } - ], - config=board_agent.board, + def writing_message(recipient, messages, sender, config): + return f"Polish the content to make an engaging and nicely formatted blog post. \n\n {recipient.chat_messages_for_summary(sender)[-1]['content']}" + + nested_chat_queue = [ + {"recipient": manager, "summary_method": "reflection_with_llm"}, + {"recipient": writer, "message": writing_message, "summary_method": "last_msg", "max_turns": 1}, + { + "recipient": reviewer, + "message": "Review the content provided.", + "summary_method": "last_msg", + "max_turns": 1, + }, + ] + assistant.register_nested_chats( + [autogen.Agent, None], + nested_chat_queue, ) - player_white.register_reply(BoardAgent, player2board_reply) - player_black.register_reply(BoardAgent, player2board_reply) - board_agent.register_reply([white_player_name, black_player_name], board_reply, 0) - autogen.initiate_chats([{"sender": player_white, "recipient": player_black, "message": "Your turn."}]) + user.initiate_chats([{"recipient": assistant, "message": tasks[0]}, {"recipient": assistant, "message": tasks[1]}]) if __name__ == "__main__": - # test_nested() - test_nested_chess() + test_nested() From 856f86a9489d51e0b0de8b77d94c5dcdd1c6008b Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Wed, 21 Feb 2024 15:42:17 -0600 Subject: [PATCH 38/57] add links to the example page --- website/docs/Examples.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/website/docs/Examples.md b/website/docs/Examples.md index 15a4d0e24b7..9fce7d43ca6 100644 --- a/website/docs/Examples.md +++ b/website/docs/Examples.md @@ -22,11 +22,16 @@ Links to notebook examples: - Automated Task Solving with Coding & Planning Agents - [View Notebook](https://github.com/microsoft/autogen/blob/main/notebook/agentchat_planning.ipynb) - Automated Task Solving with transition paths specified in a graph - [View Notebook](https://github.com/microsoft/autogen/blob/main/notebook/agentchat_graph_modelling_language_using_select_speaker.ipynb) - Running a group chat as an inner-monolgue via the SocietyOfMindAgent - [View Notebook](https://github.com/microsoft/autogen/blob/main/notebook/agentchat_society_of_mind.ipynb) + 1. **Sequential Multi-Agent Chats** - Solving Multiple Tasks in a Sequence of Chats Initiated by a Single Agent - [View Notebook](https://github.com/microsoft/autogen/blob/main/notebook/agentchat_multi_task_chats.ipynb) - Async-solving Multiple Tasks in a Sequence of Chats Initiated by a Single Agent - [View Notebook](https://github.com/microsoft/autogen/blob/main/notebook/agentchat_multi_task_async_chats.ipynb) - Solving Multiple Tasks in a Sequence of Chats Initiated by Different Agents - [View Notebook](https://github.com/microsoft/autogen/blob/main/notebook/agentchats.ipynb) +1. **Nested Chats** + - Solving Complex Tasks with Nested Chats - [View Notebook](https://github.com/microsoft/autogen/blob/main/notebook/agentchat_nestedchat.ipynb) + - OptiGuide for Solving a Supply Chain Optimization Problem with Nested Chats with a Coding Agent and a Safeguard Agent - [View Notebook](https://github.com/microsoft/autogen/blob/main/notebook/agentchat_nestedchat_optiguide.ipynb) + 1. **Applications** - Automated Chess Game Playing & Chitchatting by GPT-4 Agents - [View Notebook](https://github.com/microsoft/autogen/blob/main/notebook/agentchat_chess.ipynb) From 5cc316dc5237f008741159a1aa2044da7fd25a63 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Fri, 23 Feb 2024 22:13:15 -0500 Subject: [PATCH 39/57] annotations --- autogen/agentchat/conversable_agent.py | 27 ++++++++++++++------------ notebook/agentchat_nestedchat.ipynb | 2 +- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index 71124684925..de5efad50f7 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -323,12 +323,17 @@ def reply_func( self._ignore_async_func_in_sync_chat_list.append(reply_func) @staticmethod - def simple_nested_chat(chat_queue, recipient, messages, sender, config): + def reply_func_from_nested_chats( + chat_queue: List[Dict[str, Any]], recipient: Agent, messages: Union[str, Callable], sender: Agent, config: Any + ) -> Tuple[bool, str]: """A simple chat reply function. This function initiate one or a sequence of chats between the "recipient" and the agents in the chat_queue. It extracts and returns a summary from the nested chat based on the "summary_method" in each chat in chat_queue. + + Returns: + Tuple[bool, str]: A tuple where the first element indicates the completion of the chat, and the second element contains the summary of the last chat if any chats were initiated. """ last_msg = messages[-1].get("content", "") chat_to_run = [] @@ -352,21 +357,21 @@ def simple_nested_chat(chat_queue, recipient, messages, sender, config): def register_nested_chats( self, - trigger, - chat_queue, - reply_func_from_nested_chat="auto", + trigger: Union[Type[Agent], str, Agent, Callable[[Agent], bool], List], + chat_queue: List[Dict[str, Any]], + reply_func_from_nested_chats=reply_func_from_nested_chats, position: int = 2, **kwargs, - ): + ) -> None: """Register a nested chat reply function. Args: trigger (Agent class, str, Agent instance, callable, or list): Ref to `register_reply` for details. chat_queue (list): a list of chat objects to be initiated. - reply_func_from_nested_chat (Callable, str): the reply function for the nested chat. + reply_func_from_nested_chats (Callable, str): the reply function for the nested chat. The function takes a chat_queue for nested chat, recipient agent, a list of messages, a sender agent and a config as input and returns a reply message. - Default to `auto`, which means the a pre-defined function will be used. + Default to a built-in reply function based on nested chats. ```python - def reply_func_from_nested_chat( + def reply_func_from_nested_chats( chat_queue: List[Dict], recipient: ConversableAgent, messages: Optional[List[Dict]] = None, @@ -377,11 +382,9 @@ def reply_func_from_nested_chat( position (int): Ref to `register_reply` for details. Default to 2. It means we first check the termination and human reply, then check the registered nested chat reply. kwargs: Ref to `register_reply` for details. """ - if reply_func_from_nested_chat == "auto": - reply_func_from_nested_chat = self.simple_nested_chat - assert callable(reply_func_from_nested_chat), "reply_func_from_nested_chat must be a callable" + assert callable(reply_func_from_nested_chats), "reply_func_from_nested_chats must be a callable" - reply_func = partial(reply_func_from_nested_chat, chat_queue) + reply_func = partial(reply_func_from_nested_chats, chat_queue) self.register_reply( trigger, reply_func, diff --git a/notebook/agentchat_nestedchat.ipynb b/notebook/agentchat_nestedchat.ipynb index 464e2a5646c..5baf195ab82 100644 --- a/notebook/agentchat_nestedchat.ipynb +++ b/notebook/agentchat_nestedchat.ipynb @@ -38,7 +38,7 @@ "import autogen\n", "\n", "config_list = autogen.config_list_from_json(env_or_file=\"OAI_CONFIG_LIST\")\n", - "llm_config = {\"config_list\": config_list, \"cache_seed\": 44}\n", + "llm_config = {\"config_list\": config_list}\n", "\n", "tasks = [\n", " \"\"\"On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.\"\"\",\n", From 0221b04e1b2d501740ab8e60fb1e23b5c68e5afb Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Fri, 23 Feb 2024 23:14:55 -0500 Subject: [PATCH 40/57] documentation --- notebook/agentchat_nestedchat.ipynb | 50 +++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/notebook/agentchat_nestedchat.ipynb b/notebook/agentchat_nestedchat.ipynb index 5baf195ab82..924a74566d0 100644 --- a/notebook/agentchat_nestedchat.ipynb +++ b/notebook/agentchat_nestedchat.ipynb @@ -46,6 +46,18 @@ "]" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Since the first task could be complex to solve, we want to construct additional agents as an inner monologue, i.e., nested chats.\n", + "\n", + "### Step 1. Agent Construction\n", + "\n", + "#### Construction of a Group Chat to Be Used as an Inner Monologue\n", + "In the following code block, we construct a group chat manager which manages an inner_assistant agent and an inner_code_interpreter agent.\n" + ] + }, { "cell_type": "code", "execution_count": 10, @@ -88,6 +100,14 @@ ")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Construction of Individual Agents to Be Used in the Agent System\n", + "\n" + ] + }, { "cell_type": "code", "execution_count": 11, @@ -140,6 +160,36 @@ ")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 2: Orchestrate a Multi-Agent System with Nested Chats to Solve Tasks\n", + "\n", + "In the following code block, at the outer level, we have communication between:\n", + "\n", + "- user - assistant_1 for solving the first task, i.e., tasks[0].\n", + "- user - assistant_2 for solving the second task, i.e., tasks[1].\n", + "\n", + "Since the first task is quite complicated, we created a sequence of nested chats as the inner monologue of Assistant_1.\n", + "\n", + "- Nested Chat 1: assistant_1 - manager. This chat intends to delegate the task received by Assistant_1 to the Manager to solve.\n", + "\n", + "- Nested Chat 2: assistant_1 - writer. This chat takes the output from Nested Chat 1, i.e., Assistant_1 vs. Manager, and lets the Writer polish the content to make an engaging and nicely formatted blog post, which is realized through the writing_message function.\n", + "\n", + "- Nested Chat 3: assistant_1 - reviewer. This chat takes the output from Nested Chat 2 and intends to let the Reviewer agent review the content from Nested Chat 2.\n", + "\n", + "- Nested Chat 4: assistant_1 - writer. This chat takes the output from previous nested chats and intends to let the Writer agent finalize a blog post.\n", + "\n", + "The sequence of nested chats can be realized with the `register_nested_chats` function, which allows one to register one or a sequence of chats to a particular agent (in this example, the `assistant_1` agent).\n", + "\n", + "Information about the sequence of chats can be specified in the `chat_queue` argument of the `register_nested_chats` function. The following fields are especially useful:\n", + "- `recipient` (required) specifies the nested agent;\n", + "- `message` specifies what message to send to the nested recipient agent. In a sequence of nested chats, if the `message` field is not specified, we will use the last message the registering agent received as the initial message in the first chat and will skip any subsequent chat in the queue that does not have the `message` field. You can either provide a string or define a callable that returns a string.\n", + "- `summary_method` decides what to get out of the nested chat. You can either select from existing options including \"last_msg\" and \"reflection_with_llm\", or or define your own way on what to get from the nested chat with a Callable.\n", + "- `max_turns` determines how many turns of conversation to have between the concerned agent pairs." + ] + }, { "cell_type": "code", "execution_count": 12, From d44ebeb2e8f19dcbb19374d9509ad99501b1cbe8 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sat, 24 Feb 2024 12:00:10 -0500 Subject: [PATCH 41/57] default values --- autogen/agentchat/conversable_agent.py | 4 +- notebook/agentchat_nestedchat.ipynb | 565 ++++++++++++------------- 2 files changed, 283 insertions(+), 286 deletions(-) diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index de5efad50f7..dccbe664dda 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -335,7 +335,7 @@ def reply_func_from_nested_chats( Returns: Tuple[bool, str]: A tuple where the first element indicates the completion of the chat, and the second element contains the summary of the last chat if any chats were initiated. """ - last_msg = messages[-1].get("content", "") + last_msg = messages[-1].get("content") chat_to_run = [] for i, c in enumerate(chat_queue): current_c = c.copy() @@ -344,7 +344,7 @@ def reply_func_from_nested_chats( message = message(recipient, messages, sender, config) # If message is not provided in chat_queue, we by default use the last message from the original chat history as the first message in this nested chat (for the first chat in the chat queue). # NOTE: This setting is prone to change. - if message is None and i == 0: + if message is None and i == 0 and last_msg: message = last_msg # We only run chat that has a valid message. NOTE: This is prone to change dependin on applications. if message: diff --git a/notebook/agentchat_nestedchat.ipynb b/notebook/agentchat_nestedchat.ipynb index 924a74566d0..fa7c2fc7969 100644 --- a/notebook/agentchat_nestedchat.ipynb +++ b/notebook/agentchat_nestedchat.ipynb @@ -31,7 +31,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 13, "metadata": {}, "outputs": [], "source": [ @@ -60,7 +60,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -110,7 +110,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 15, "metadata": {}, "outputs": [], "source": [ @@ -192,7 +192,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 16, "metadata": {}, "outputs": [ { @@ -226,135 +226,110 @@ "\n", "On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.\n", "\n", - "--------------------------------------------------------------------------------\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/qingyunwu/Documents/github/autogen/autogen/agentchat/chat.py:79: UserWarning: Repetitive recipients detected: The chat history will be cleared by default if a recipient appears more than once. To retain the chat history, please set 'clear_history=False' in the configuration of the repeating agent.\n", + " warnings.warn(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", "\n", - "To find out on which specific days in 2024 Microsoft stock (ticker symbol: MSFT) was trading higher than $400, we will need historical stock price data for Microsoft for the year 2024. This data is available from financial market data providers like Yahoo Finance, Google Finance, or other financial APIs.\n", + "To find out on which specific days Microsoft's stock (ticker symbol: MSFT) closed above $400 in 2024, we will need to access historical stock price data for Microsoft from a reliable financial data source. This kind of data is typically available on financial websites such as Yahoo Finance, Google Finance, or through financial data APIs such as Alpha Vantage or IEX Cloud.\n", "\n", - "Given that I can't browse the web or access an API directly, I will provide you with Python code that you can execute to retrieve the data from Yahoo Finance (using the `yfinance` library) and then analyze it to determine on which days Microsoft's stock price exceeded $400.\n", + "Since I cannot directly browse the web or access external APIs, I will provide you with a Python script that uses the `yfinance` library to fetch historical stock data for Microsoft and filter the dates when the stock price was higher than $400.\n", "\n", - "Before running the code, you will need to ensure that you have the `yfinance` library installed on your system. You can install it using pip if you haven't got it already:\n", - "```sh\n", - "pip install yfinance\n", - "```\n", + "Firstly, you need to install the `yfinance` module if it's not already installed. You can do so using pip by executing `pip install yfinance` in your shell.\n", "\n", - "Now, here's the Python code to retrieve Microsoft stock data for the year 2024 and find out when it was higher than $400:\n", + "After installation, here is the Python code that you can execute to fetch the data and filter the specific dates:\n", "\n", "```python\n", "# filename: msft_stock_analysis.py\n", "import yfinance as yf\n", "from datetime import datetime\n", "\n", - "# Define the ticker symbol for Microsoft and the threshold stock price\n", - "ticker_symbol = 'MSFT'\n", - "threshold_price = 400\n", - "\n", - "# Define the start and end dates for the year 2024\n", - "start_date = '2024-01-01'\n", - "end_date = '2024-12-31'\n", + "# Define the ticker symbol for Microsoft\n", + "ticker_symbol = \"MSFT\"\n", "\n", - "# Fetch the historical data for Microsoft for 2024\n", - "msft = yf.Ticker(ticker_symbol)\n", - "msft_history = msft.history(start=start_date, end=end_date)\n", + "# Fetch historical stock data for Microsoft for the year 2024\n", + "start_date = \"2024-01-01\"\n", + "end_date = \"2024-12-31\"\n", + "msft_data = yf.download(ticker_symbol, start=start_date, end=end_date)\n", "\n", - "# Find the days when the stock was higher than $400\n", - "high_days = msft_history[msft_history['Close'] > threshold_price]\n", + "# Filter the days when the closing price was higher than $400\n", + "days_above_400 = msft_data[msft_data['Close'] > 400]\n", "\n", - "# Print out the dates and closing prices\n", - "for date, row in high_days.iterrows():\n", - " print(f\"Date: {date.strftime('%Y-%m-%d')}, Close: ${row['Close']:.2f}\")\n", + "# Print the dates and closing prices on those dates\n", + "print(\"Dates when Microsoft's stock closed above $400 in 2024:\")\n", + "print(days_above_400[['Close']])\n", "\n", - "# Comment on the stock performance if there's data available\n", - "if not high_days.empty:\n", - " first_day = high_days.index[0].strftime('%Y-%m-%d')\n", - " last_day = high_days.index[-1].strftime('%Y-%m-%d')\n", - " print(f\"\\nMicrosoft stock was higher than $400 on {len(high_days)} days between {first_day} and {last_day} in 2024.\")\n", + "# Comment on the stock performance\n", + "if not days_above_400.empty:\n", + " days_count = len(days_above_400)\n", + " print(f\"\\nMicrosoft's stock closed above $400 on {days_count} days in 2024.\")\n", "else:\n", - " print(\"\\nMicrosoft stock did not trade above $400 in 2024.\")\n", + " print(\"\\nMicrosoft's stock did not close above $400 on any day in 2024.\")\n", "```\n", "\n", - "Please run this Python code, and it will output the dates on which Microsoft's stock price was higher than $400 in 2024, as well as a comment on the stock performance based on the available data. If you encounter any issues with the execution, please let me know so I can assist you further.\n", + "Please save the above code to a file named `msft_stock_analysis.py` and run it. This will print out the dates when Microsoft's stock was higher than $400 in 2024 and comment on the stock performance based on the results. \n", + "\n", + "Keep in mind that the `yfinance` library fetches real-time data, and since we are in 2023, data for 2024 is not available yet. Therefore, when you run the script, it will show that the stock did not close above $400, which is expected since 2024 has not occurred yet. For actual analysis, you would have to run the script after the end of 2024 or modify the date range to a past range where data is available.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", - ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is sh)...\u001b[0m\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/qingyunwu/Documents/github/autogen/autogen/agentchat/chat.py:79: UserWarning: Repetitive recipients detected: The chat history will be cleared by default if a recipient appears more than once. To retain the chat history, please set 'clear_history=False' in the configuration of the repeating agent.\n", - " warnings.warn(\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "\u001b[31m\n", - ">>>>>>>> EXECUTING CODE BLOCK 1 (inferred language is python)...\u001b[0m\n", + ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n", "\u001b[33mInner-code-interpreter\u001b[0m (to chat_manager):\n", "\n", "exitcode: 0 (execution succeeded)\n", "Code output: \n", - "Requirement already satisfied: yfinance in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (0.2.36)\n", - "Requirement already satisfied: pandas>=1.3.0 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from yfinance) (2.2.0)\n", - "Requirement already satisfied: numpy>=1.16.5 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from yfinance) (1.26.4)\n", - "Requirement already satisfied: requests>=2.31 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from yfinance) (2.31.0)\n", - "Requirement already satisfied: multitasking>=0.0.7 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from yfinance) (0.0.11)\n", - "Requirement already satisfied: lxml>=4.9.1 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from yfinance) (5.1.0)\n", - "Requirement already satisfied: appdirs>=1.4.4 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from yfinance) (1.4.4)\n", - "Requirement already satisfied: pytz>=2022.5 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from yfinance) (2024.1)\n", - "Requirement already satisfied: frozendict>=2.3.4 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from yfinance) (2.4.0)\n", - "Requirement already satisfied: peewee>=3.16.2 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from yfinance) (3.17.1)\n", - "Requirement already satisfied: beautifulsoup4>=4.11.1 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from yfinance) (4.12.3)\n", - "Requirement already satisfied: html5lib>=1.1 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from yfinance) (1.1)\n", - "Requirement already satisfied: soupsieve>1.2 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from beautifulsoup4>=4.11.1->yfinance) (2.5)\n", - "Requirement already satisfied: six>=1.9 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from html5lib>=1.1->yfinance) (1.16.0)\n", - "Requirement already satisfied: webencodings in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from html5lib>=1.1->yfinance) (0.5.1)\n", - "Requirement already satisfied: python-dateutil>=2.8.2 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from pandas>=1.3.0->yfinance) (2.8.2)\n", - "Requirement already satisfied: tzdata>=2022.7 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from pandas>=1.3.0->yfinance) (2024.1)\n", - "Requirement already satisfied: charset-normalizer<4,>=2 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from requests>=2.31->yfinance) (3.3.2)\n", - "Requirement already satisfied: idna<4,>=2.5 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from requests>=2.31->yfinance) (3.6)\n", - "Requirement already satisfied: urllib3<3,>=1.21.1 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from requests>=2.31->yfinance) (1.26.18)\n", - "Requirement already satisfied: certifi>=2017.4.17 in /Users/qingyunwu/miniconda3/envs/ag/lib/python3.10/site-packages (from requests>=2.31->yfinance) (2024.2.2)\n", - "\n", - "Date: 2024-01-24, Close: $401.82\n", - "Date: 2024-01-25, Close: $404.12\n", - "Date: 2024-01-26, Close: $403.18\n", - "Date: 2024-01-29, Close: $408.96\n", - "Date: 2024-01-30, Close: $407.84\n", - "Date: 2024-02-01, Close: $403.03\n", - "Date: 2024-02-02, Close: $410.46\n", - "Date: 2024-02-05, Close: $404.90\n", - "Date: 2024-02-06, Close: $404.74\n", - "Date: 2024-02-07, Close: $413.29\n", - "Date: 2024-02-08, Close: $413.35\n", - "Date: 2024-02-09, Close: $419.77\n", - "Date: 2024-02-12, Close: $414.49\n", - "Date: 2024-02-13, Close: $405.57\n", - "Date: 2024-02-14, Close: $409.49\n", - "Date: 2024-02-15, Close: $406.56\n", - "Date: 2024-02-16, Close: $404.06\n", - "Date: 2024-02-20, Close: $402.79\n", - "Date: 2024-02-21, Close: $400.38\n", - "\n", - "Microsoft stock was higher than $400 on 19 days between 2024-01-24 and 2024-02-21 in 2024.\n", + "Dates when Microsoft's stock closed above $400 in 2024:\n", + " Close\n", + "Date \n", + "2024-01-24 402.559998\n", + "2024-01-25 404.869995\n", + "2024-01-26 403.929993\n", + "2024-01-29 409.720001\n", + "2024-01-30 408.589996\n", + "2024-02-01 403.779999\n", + "2024-02-02 411.220001\n", + "2024-02-05 405.649994\n", + "2024-02-06 405.489990\n", + "2024-02-07 414.049988\n", + "2024-02-08 414.109985\n", + "2024-02-09 420.549988\n", + "2024-02-12 415.260010\n", + "2024-02-13 406.320007\n", + "2024-02-14 409.489990\n", + "2024-02-15 406.559998\n", + "2024-02-16 404.059998\n", + "2024-02-20 402.790009\n", + "2024-02-21 402.179993\n", + "2024-02-22 411.649994\n", + "2024-02-23 410.339996\n", + "\n", + "Microsoft's stock closed above $400 on 21 days in 2024.\n", "\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", "\n", - "The execution of the provided code indicates that Microsoft stock (MSFT) was trading higher than $400 on 19 days between January 24, 2024, and February 21, 2024. Here is an overview of the stock performance during this period:\n", + "Based on the output from the code execution, Microsoft's stock (MSFT) closed above $400 on 21 separate days in 2024. The closing prices on those days ranged from slightly above $400 to highs around $420. This indicates periods of strong performance, during which the stock reached significant value.\n", + "\n", + "Here is a brief comment on the stock performance:\n", "\n", - "- **Peak Price**: The highest closing price recorded was $419.77 on February 9, 2024.\n", - "- **Stability Above $400**: The stock maintained its value above $400 on multiple consecutive days.\n", - "- **Volatility**: There were fluctuations in the stock price, with it crossing the $400 threshold multiple times.\n", + "The data shows that Microsoft's stock had several instances where it exceeded the $400 mark in early 2024, with notable peaks in February, which suggests a bullish trend during that period. Specific days like February 9th, where the stock hit around $420, indicate particularly strong performance.\n", "\n", - "Based on the output, we can infer that during the start of 2024, MSFT experienced periods of strong performance with its share price reaching above the $400 mark, highlighting investor confidence or positive market conditions for the company at that time. However, without further contextual information or a comparison over a more extended period, it is challenging to make a comprehensive assessment of the stock's overall performance.\n", + "Having such a number of days with closing prices above $400 signals a robust market valuation for Microsoft at those times. Investors who held the stock on these dates may have seen appreciable gains, depending on their portfolio strategies.\n", "\n", - "For a more detailed analysis, you would typically look at additional information such as the company's earnings reports, news, market trends, and broader economic indicators. Overall, the short window provided suggests a positive trend for Microsoft during the early part of 2024.\n", + "It's also noteworthy that stock prices fluctuate due to a variety of factors, including market sentiment, company performance, industry trends, and general economic conditions. This data provides a snapshot of Microsoft's stock performance but a deeper analysis would require more context on the market conditions, news, and events surrounding these dates.\n", "\n", "TERMINATE\n", "\n", @@ -367,7 +342,7 @@ " On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.\n", "\n", "With the following carryover: \n", - "The Python code execution shows Microsoft stock (MSFT) exceeded $400 on 19 days from January 24, 2024, to February 21, 2024, with the highest closing price being $419.77 on February 9, 2024. The stock displayed periods of solid performance and stability above $400 during this timeframe, indicating positive market conditions or investor confidence in Microsoft's performance at the start of 2024.\u001b[0m\n", + "Microsoft's stock (MSFT) closed above $400 on 21 separate days in 2024, demonstrating periods of strong stock performance with values reaching as high as around $420. The stock data suggests a bullish trend, especially in February when the stock peaked, indicating positive investor sentiment and robust market valuation during those times.\u001b[0m\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", "\u001b[33mAssistant_1\u001b[0m (to Writer):\n", @@ -376,26 +351,38 @@ "\n", " On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.\n", "Context: \n", - "The Python code execution shows Microsoft stock (MSFT) exceeded $400 on 19 days from January 24, 2024, to February 21, 2024, with the highest closing price being $419.77 on February 9, 2024. The stock displayed periods of solid performance and stability above $400 during this timeframe, indicating positive market conditions or investor confidence in Microsoft's performance at the start of 2024.\n", + "Microsoft's stock (MSFT) closed above $400 on 21 separate days in 2024, demonstrating periods of strong stock performance with values reaching as high as around $420. The stock data suggests a bullish trend, especially in February when the stock peaked, indicating positive investor sentiment and robust market valuation during those times.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mWriter\u001b[0m (to Assistant_1):\n", "\n", - "Title: **Microsoft's Ascension: Stock Soars Above $400 in Early 2024**\n", + "# Beyond the $400 Mark: Tracing Microsoft's Pinnacle Moments in 2024\n", + "\n", + "In the ever-fluctuating world of stocks, each milestone crossed can turn into a celebration or a stern reckoning of what the future holds. For Microsoft (MSFT), the year 2024 was a testament to its resilience and investor confidence as its stock ascended beyond the coveted $400 mark on 21 remarkable occasions.\n", + "\n", + "This performance wasn't just numerically significant; it was emblematic of a potent bullish energy within the market, affirming the tech giant’s leviathan presence amidst a highly competitive industry terrain. The loftiest point, flirting with the heights of approximately $420, served not just as a numerical peak, but as a beacon of the company’s market valuation and the unwavering investor trust in Microsoft's strategies and future prospects.\n", + "\n", + "## A Snapshot of Stellar Performance\n", + "\n", + "Microsoft's stock performance throughout 2024 was far from being a serendipitous spike. Instead, it showcased a carefully constructed narrative of growth and optimism. The chronicles of MSFT's venture past the $400 threshold are marked by fleeting dips and steadfast ascensions. Remarkably, the surge wasn't some scattergun pattern scattered across the calendar year. Rather, it was most pronounced in the month of February — a period that could very well be termed as the 'February Flourish.'\n", "\n", - "In the upper echelons of the stock market, Microsoft Corporation (MSFT) stands as a gauge of innovative prowess and economic vitality. Its shares are more than just pieces of paper; they represent the confidence and aspirations of investors worldwide. It takes a remarkable convergence of market conditions and corporate triumphs for a stock to breach a milestone as significant as the $400 mark. Microsoft stock not only surpassed this threshold but did so with exceptional poise, ushering in a period of celebration for shareholders in the early months of 2024.\n", + "During this short yet significant timeframe, investors witnessed the creation of incremental wealth that possibly outstripped all market expectations, setting a precedent for what a legacy company like Microsoft could achieve in a climate rife with disruptors and innovators.\n", "\n", - "Between January 24th and February 21st, Microsoft’s stock price proudly exceeded the $400 benchmark on 19 different occasions. This was not a fleeting brush with success but a testament to the tech giant’s enduring magnetism in the market. The crescendo of this symphony of gains was the stunning closing price of $419.77 on February 9th, marking the pinnacle of Microsoft’s financial performance during this period.\n", + "## Of Peaks and Valleys\n", "\n", - "As the graph of MSFT traced an enviable trajectory, market analysts and investors alike were reminded of the intrinsic strength bolstering Microsoft’s market position. This impressive trend was no accident; it was the result of a carefully balanced equation of innovation, strategic leadership, and market dynamics. Each day above $400 whispered a story of unwavering trust in Microsoft's trajectory and an underlying confidence in its ability to leverage opportunities, whether they lay in cloud computing, artificial intelligence, gaming, or productivity tools.\n", + "When assessing the grand canvas of Microsoft's stock performance, it is crucial to take a step back and appreciate the layered complexity of these financial movements. Amidst economic uncertainties and the technocratic whims that habitually serve both as tailwinds and headwinds to such companies, the story of Microsoft crossing the $400 threshold imbues the narrative with contours of investor euphoria interlaced with strategic agility.\n", "\n", - "The persistent performance of MSFT not only indicated the vitality of Microsoft as a corporation but also reflected wider economic currents. In the dance of the stock market, every jump and dip is a response to the music played by investor sentiment, technological advancements, and global economic trends. To stay above $400 for a sustained period is to ride the crest of these forces, showcasing an alignment with the progress investors believe technology will undeniably drive in our everyday lives.\n", + "As these 21 days get etched into the fiscal annals of Microsoft, one cannot help but ponder the fine blend of factors — ranging from market dynamics to Microsoft's organizational maneuvers — that have coalesced to make these numbers a reality.\n", "\n", - "As we dissect these moments of triumph, we find the essence of Microsoft's financial fortitude. It's a blend of continuous innovation, well-calibrated risk management, and the company's intrinsic ability to evolve in the fast-paced high-tech arena. With products and services that have achieved a ubiquitous presence, Microsoft has been sculpting the future, byte by byte, in a world voraciously hungry for digital transformation.\n", + "## A Convergence of Positive Sentiments\n", "\n", - "The million-dollar question remains: Can Microsoft maintain this stellar performance throughout 2024? While forecasts are invariably tinged with uncertainty, if the company's illustrious record of targeting burgeoning markets and expanding its influence is anything to go by, then anticipation is in the air. Investors watch with bated breath, pondering whether this IT titan will continue to climb or find this peak soon eclipsed by even higher summits.\n", + "What does the future hold for MSFT as it treads past this impressive benchmark? The takeaway is not a simple extrapolation of past performance. Rather, it's the realization that stock trajectories, particularly for a company like Microsoft, are a confluence of judicious corporate governance, product innovation, market anticipation, and, dare we say, a touch of the zeitgeist.\n", "\n", - "As we turn the page on this chapter of Microsoft's fiscal saga, the only certainty is that the journey up the stock charts is as dynamic as the technologies the company masterfully crafts. Will the stock continue its flight, or will it face the gravity of market corrections? Only time will tell, but for now, Microsoft has given its investors reason to bask in the warm glow of market triumph.\n", + "Microsoft’s capacity to command such stock valitudes speaks volumes about its robust position in both the technology domain and the stock market ecosystem. Whether this fiscal feat can be sustained or surpassed remains a subject of much conjecture and anticipation.\n", + "\n", + "As we continue to navigate the ebbs and flows of an unpredictable market landscape, the days when Microsoft's stock rose gallantly above $400 will be recalled as milestones of 2024, celebrated reflections of a corporation sailing the high seas of market volatility with remarkable poise.\n", + "\n", + "The analysis of precise data points and enlightening interpretations reveal much about the past while providing distinctive glimpses of potential futures. With Microsoft standing tall amongst peers, its voyage above the $400 watermark will be chronicled as days of high tides in the annals of financial history.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[34m\n", @@ -404,79 +391,101 @@ "Review the content provided.\n", "\n", "With the following carryover: \n", - "The Python code execution shows Microsoft stock (MSFT) exceeded $400 on 19 days from January 24, 2024, to February 21, 2024, with the highest closing price being $419.77 on February 9, 2024. The stock displayed periods of solid performance and stability above $400 during this timeframe, indicating positive market conditions or investor confidence in Microsoft's performance at the start of 2024.\n", - "Title: **Microsoft's Ascension: Stock Soars Above $400 in Early 2024**\n", + "Microsoft's stock (MSFT) closed above $400 on 21 separate days in 2024, demonstrating periods of strong stock performance with values reaching as high as around $420. The stock data suggests a bullish trend, especially in February when the stock peaked, indicating positive investor sentiment and robust market valuation during those times.\n", + "# Beyond the $400 Mark: Tracing Microsoft's Pinnacle Moments in 2024\n", + "\n", + "In the ever-fluctuating world of stocks, each milestone crossed can turn into a celebration or a stern reckoning of what the future holds. For Microsoft (MSFT), the year 2024 was a testament to its resilience and investor confidence as its stock ascended beyond the coveted $400 mark on 21 remarkable occasions.\n", + "\n", + "This performance wasn't just numerically significant; it was emblematic of a potent bullish energy within the market, affirming the tech giant’s leviathan presence amidst a highly competitive industry terrain. The loftiest point, flirting with the heights of approximately $420, served not just as a numerical peak, but as a beacon of the company’s market valuation and the unwavering investor trust in Microsoft's strategies and future prospects.\n", + "\n", + "## A Snapshot of Stellar Performance\n", "\n", - "In the upper echelons of the stock market, Microsoft Corporation (MSFT) stands as a gauge of innovative prowess and economic vitality. Its shares are more than just pieces of paper; they represent the confidence and aspirations of investors worldwide. It takes a remarkable convergence of market conditions and corporate triumphs for a stock to breach a milestone as significant as the $400 mark. Microsoft stock not only surpassed this threshold but did so with exceptional poise, ushering in a period of celebration for shareholders in the early months of 2024.\n", + "Microsoft's stock performance throughout 2024 was far from being a serendipitous spike. Instead, it showcased a carefully constructed narrative of growth and optimism. The chronicles of MSFT's venture past the $400 threshold are marked by fleeting dips and steadfast ascensions. Remarkably, the surge wasn't some scattergun pattern scattered across the calendar year. Rather, it was most pronounced in the month of February — a period that could very well be termed as the 'February Flourish.'\n", "\n", - "Between January 24th and February 21st, Microsoft’s stock price proudly exceeded the $400 benchmark on 19 different occasions. This was not a fleeting brush with success but a testament to the tech giant’s enduring magnetism in the market. The crescendo of this symphony of gains was the stunning closing price of $419.77 on February 9th, marking the pinnacle of Microsoft’s financial performance during this period.\n", + "During this short yet significant timeframe, investors witnessed the creation of incremental wealth that possibly outstripped all market expectations, setting a precedent for what a legacy company like Microsoft could achieve in a climate rife with disruptors and innovators.\n", "\n", - "As the graph of MSFT traced an enviable trajectory, market analysts and investors alike were reminded of the intrinsic strength bolstering Microsoft’s market position. This impressive trend was no accident; it was the result of a carefully balanced equation of innovation, strategic leadership, and market dynamics. Each day above $400 whispered a story of unwavering trust in Microsoft's trajectory and an underlying confidence in its ability to leverage opportunities, whether they lay in cloud computing, artificial intelligence, gaming, or productivity tools.\n", + "## Of Peaks and Valleys\n", "\n", - "The persistent performance of MSFT not only indicated the vitality of Microsoft as a corporation but also reflected wider economic currents. In the dance of the stock market, every jump and dip is a response to the music played by investor sentiment, technological advancements, and global economic trends. To stay above $400 for a sustained period is to ride the crest of these forces, showcasing an alignment with the progress investors believe technology will undeniably drive in our everyday lives.\n", + "When assessing the grand canvas of Microsoft's stock performance, it is crucial to take a step back and appreciate the layered complexity of these financial movements. Amidst economic uncertainties and the technocratic whims that habitually serve both as tailwinds and headwinds to such companies, the story of Microsoft crossing the $400 threshold imbues the narrative with contours of investor euphoria interlaced with strategic agility.\n", "\n", - "As we dissect these moments of triumph, we find the essence of Microsoft's financial fortitude. It's a blend of continuous innovation, well-calibrated risk management, and the company's intrinsic ability to evolve in the fast-paced high-tech arena. With products and services that have achieved a ubiquitous presence, Microsoft has been sculpting the future, byte by byte, in a world voraciously hungry for digital transformation.\n", + "As these 21 days get etched into the fiscal annals of Microsoft, one cannot help but ponder the fine blend of factors — ranging from market dynamics to Microsoft's organizational maneuvers — that have coalesced to make these numbers a reality.\n", "\n", - "The million-dollar question remains: Can Microsoft maintain this stellar performance throughout 2024? While forecasts are invariably tinged with uncertainty, if the company's illustrious record of targeting burgeoning markets and expanding its influence is anything to go by, then anticipation is in the air. Investors watch with bated breath, pondering whether this IT titan will continue to climb or find this peak soon eclipsed by even higher summits.\n", + "## A Convergence of Positive Sentiments\n", "\n", - "As we turn the page on this chapter of Microsoft's fiscal saga, the only certainty is that the journey up the stock charts is as dynamic as the technologies the company masterfully crafts. Will the stock continue its flight, or will it face the gravity of market corrections? Only time will tell, but for now, Microsoft has given its investors reason to bask in the warm glow of market triumph.\u001b[0m\n", + "What does the future hold for MSFT as it treads past this impressive benchmark? The takeaway is not a simple extrapolation of past performance. Rather, it's the realization that stock trajectories, particularly for a company like Microsoft, are a confluence of judicious corporate governance, product innovation, market anticipation, and, dare we say, a touch of the zeitgeist.\n", + "\n", + "Microsoft’s capacity to command such stock valitudes speaks volumes about its robust position in both the technology domain and the stock market ecosystem. Whether this fiscal feat can be sustained or surpassed remains a subject of much conjecture and anticipation.\n", + "\n", + "As we continue to navigate the ebbs and flows of an unpredictable market landscape, the days when Microsoft's stock rose gallantly above $400 will be recalled as milestones of 2024, celebrated reflections of a corporation sailing the high seas of market volatility with remarkable poise.\n", + "\n", + "The analysis of precise data points and enlightening interpretations reveal much about the past while providing distinctive glimpses of potential futures. With Microsoft standing tall amongst peers, its voyage above the $400 watermark will be chronicled as days of high tides in the annals of financial history.\u001b[0m\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", "\u001b[33mAssistant_1\u001b[0m (to Reviewer):\n", "\n", "Review the content provided.\n", "Context: \n", - "The Python code execution shows Microsoft stock (MSFT) exceeded $400 on 19 days from January 24, 2024, to February 21, 2024, with the highest closing price being $419.77 on February 9, 2024. The stock displayed periods of solid performance and stability above $400 during this timeframe, indicating positive market conditions or investor confidence in Microsoft's performance at the start of 2024.\n", - "Title: **Microsoft's Ascension: Stock Soars Above $400 in Early 2024**\n", + "Microsoft's stock (MSFT) closed above $400 on 21 separate days in 2024, demonstrating periods of strong stock performance with values reaching as high as around $420. The stock data suggests a bullish trend, especially in February when the stock peaked, indicating positive investor sentiment and robust market valuation during those times.\n", + "# Beyond the $400 Mark: Tracing Microsoft's Pinnacle Moments in 2024\n", + "\n", + "In the ever-fluctuating world of stocks, each milestone crossed can turn into a celebration or a stern reckoning of what the future holds. For Microsoft (MSFT), the year 2024 was a testament to its resilience and investor confidence as its stock ascended beyond the coveted $400 mark on 21 remarkable occasions.\n", + "\n", + "This performance wasn't just numerically significant; it was emblematic of a potent bullish energy within the market, affirming the tech giant’s leviathan presence amidst a highly competitive industry terrain. The loftiest point, flirting with the heights of approximately $420, served not just as a numerical peak, but as a beacon of the company’s market valuation and the unwavering investor trust in Microsoft's strategies and future prospects.\n", "\n", - "In the upper echelons of the stock market, Microsoft Corporation (MSFT) stands as a gauge of innovative prowess and economic vitality. Its shares are more than just pieces of paper; they represent the confidence and aspirations of investors worldwide. It takes a remarkable convergence of market conditions and corporate triumphs for a stock to breach a milestone as significant as the $400 mark. Microsoft stock not only surpassed this threshold but did so with exceptional poise, ushering in a period of celebration for shareholders in the early months of 2024.\n", + "## A Snapshot of Stellar Performance\n", "\n", - "Between January 24th and February 21st, Microsoft’s stock price proudly exceeded the $400 benchmark on 19 different occasions. This was not a fleeting brush with success but a testament to the tech giant’s enduring magnetism in the market. The crescendo of this symphony of gains was the stunning closing price of $419.77 on February 9th, marking the pinnacle of Microsoft’s financial performance during this period.\n", + "Microsoft's stock performance throughout 2024 was far from being a serendipitous spike. Instead, it showcased a carefully constructed narrative of growth and optimism. The chronicles of MSFT's venture past the $400 threshold are marked by fleeting dips and steadfast ascensions. Remarkably, the surge wasn't some scattergun pattern scattered across the calendar year. Rather, it was most pronounced in the month of February — a period that could very well be termed as the 'February Flourish.'\n", "\n", - "As the graph of MSFT traced an enviable trajectory, market analysts and investors alike were reminded of the intrinsic strength bolstering Microsoft’s market position. This impressive trend was no accident; it was the result of a carefully balanced equation of innovation, strategic leadership, and market dynamics. Each day above $400 whispered a story of unwavering trust in Microsoft's trajectory and an underlying confidence in its ability to leverage opportunities, whether they lay in cloud computing, artificial intelligence, gaming, or productivity tools.\n", + "During this short yet significant timeframe, investors witnessed the creation of incremental wealth that possibly outstripped all market expectations, setting a precedent for what a legacy company like Microsoft could achieve in a climate rife with disruptors and innovators.\n", "\n", - "The persistent performance of MSFT not only indicated the vitality of Microsoft as a corporation but also reflected wider economic currents. In the dance of the stock market, every jump and dip is a response to the music played by investor sentiment, technological advancements, and global economic trends. To stay above $400 for a sustained period is to ride the crest of these forces, showcasing an alignment with the progress investors believe technology will undeniably drive in our everyday lives.\n", + "## Of Peaks and Valleys\n", "\n", - "As we dissect these moments of triumph, we find the essence of Microsoft's financial fortitude. It's a blend of continuous innovation, well-calibrated risk management, and the company's intrinsic ability to evolve in the fast-paced high-tech arena. With products and services that have achieved a ubiquitous presence, Microsoft has been sculpting the future, byte by byte, in a world voraciously hungry for digital transformation.\n", + "When assessing the grand canvas of Microsoft's stock performance, it is crucial to take a step back and appreciate the layered complexity of these financial movements. Amidst economic uncertainties and the technocratic whims that habitually serve both as tailwinds and headwinds to such companies, the story of Microsoft crossing the $400 threshold imbues the narrative with contours of investor euphoria interlaced with strategic agility.\n", "\n", - "The million-dollar question remains: Can Microsoft maintain this stellar performance throughout 2024? While forecasts are invariably tinged with uncertainty, if the company's illustrious record of targeting burgeoning markets and expanding its influence is anything to go by, then anticipation is in the air. Investors watch with bated breath, pondering whether this IT titan will continue to climb or find this peak soon eclipsed by even higher summits.\n", + "As these 21 days get etched into the fiscal annals of Microsoft, one cannot help but ponder the fine blend of factors — ranging from market dynamics to Microsoft's organizational maneuvers — that have coalesced to make these numbers a reality.\n", "\n", - "As we turn the page on this chapter of Microsoft's fiscal saga, the only certainty is that the journey up the stock charts is as dynamic as the technologies the company masterfully crafts. Will the stock continue its flight, or will it face the gravity of market corrections? Only time will tell, but for now, Microsoft has given its investors reason to bask in the warm glow of market triumph.\n", + "## A Convergence of Positive Sentiments\n", + "\n", + "What does the future hold for MSFT as it treads past this impressive benchmark? The takeaway is not a simple extrapolation of past performance. Rather, it's the realization that stock trajectories, particularly for a company like Microsoft, are a confluence of judicious corporate governance, product innovation, market anticipation, and, dare we say, a touch of the zeitgeist.\n", + "\n", + "Microsoft’s capacity to command such stock valitudes speaks volumes about its robust position in both the technology domain and the stock market ecosystem. Whether this fiscal feat can be sustained or surpassed remains a subject of much conjecture and anticipation.\n", + "\n", + "As we continue to navigate the ebbs and flows of an unpredictable market landscape, the days when Microsoft's stock rose gallantly above $400 will be recalled as milestones of 2024, celebrated reflections of a corporation sailing the high seas of market volatility with remarkable poise.\n", + "\n", + "The analysis of precise data points and enlightening interpretations reveal much about the past while providing distinctive glimpses of potential futures. With Microsoft standing tall amongst peers, its voyage above the $400 watermark will be chronicled as days of high tides in the annals of financial history.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mReviewer\u001b[0m (to Assistant_1):\n", "\n", - "As a compliance reviewer, my key focus in examining the provided content is to ensure that the material does not contain any misinformation, unsubstantiated claims, regulatory violations, or language that could be deemed harmful or misleading to consumers or investors. The review also includes a check for broader ethical considerations such as fairness, bias, and language appropriateness.\n", + "The content provided serves as a market commentary, analyzing Microsoft's stock performance in the year 2024. In this review, I must ensure that the content is free from harmful elements, false claims, or regulatory violations.\n", "\n", - "Review Summary:\n", + "### Potential issues to consider:\n", "\n", - "1. **Accuracy of Information**: The content references specific dates and stock prices which are factual claims. These should be cross-verified with actual market data to ensure the information is correct. False or outdated information could mislead investors.\n", + "1. **Accuracy of Data**: Claims about stock prices and the number of days the stock closed above a certain threshold should be verifiable with stock market data. There must be confirmation that these numbers are accurate to present a truthful report and avoid misleading investors or stakeholders.\n", "\n", - "2. **Investment Advice and Predictions**: The content should avoid making predictions or offering investment advice unless it is a regulated advisor with the appropriate disclaimers. Phrases like the \"million-dollar question\" and speculating on future performance need to be examined within the context of providing financial advice.\n", + "2. **Forward-Looking Statements**: The article should avoid overly optimistic or pessimistic conjectures about future stock performance, which can be perceived as investment advice. Speculative statements should be clearly marked as such and provide a disclaimer that past performance does not guarantee future results.\n", "\n", - "3. **Forward-looking Statements**: The content includes forward-looking statements about Microsoft's future performance. It's important to include a disclaimer that past performance is not indicative of future results, to ensure compliance with financial communication regulations.\n", + "3. **Fair and Balanced**: While the article largely celebrates Microsoft's performance, it should also acknowledge risks and uncertainties inherent in stock investments to maintain a fair and balanced approach without unduly influencing market sentiment.\n", "\n", - "4. **Promotional Language**: The title \"Microsoft's Ascension\" and descriptive phrases like \"ushering in a period of celebration for shareholders\" might be too promotional and could be seen as encouraging investment. The content should present a balanced view without overly promotional language.\n", + "4. **Non-Promotional Language**: Ensure that the content doesn't come off as promotional. Phrases like \"Microsoft's resilience and investor confidence\" and \"celebrated reflections of a corporation sailing the high seas of market volatility with remarkable poise\" should be reviewed to avoid appearing biased or overly promotional.\n", "\n", - "5. **Balance and Fairness**: While the content praises Microsoft's performance, it should also fairly acknowledge market risks and realities without guaranteeing future results. Trust and confidence statements must be substantiated and not imply unrealistically high expectations.\n", + "5. **Compliance with SEC Regulations**: In the United States, Securities and Exchange Commission (SEC) regulations require careful communication about stock performance to avoid market manipulation, insider trading implications, or misstatements.\n", "\n", - "6. **Use of Metaphors and Language**: The content is rich with metaphors and expressive language (e.g., \"symphony of gains,\" \"dance of the stock market\"). While engaging, caution is warranted to ensure that the metaphors do not obscure facts, mislead, or overstate the stability and potential of an investment.\n", + "6. **Objective Tone**: While the narrative is engaging, it's important that it remains objective and does not use language that could be considered subjective or biased. Phrases like \"Microsoft’s leviathan presence\" or \"February Flourish\" might be interpreted as adding emotional color rather than stating facts.\n", "\n", - "7. **Market Manipulation**: There appears to be no intent to manipulate the market, but the optimistic tone could unduly influence investor decisions. A compliance reviewer should ensure the statements have basis and are not intended to artificially inflate stock value.\n", + "7. **Clear Attribution**: If the report is based on external analyses or reported events, these should be clearly cited and attributed to maintain transparency.\n", "\n", - "8. **Disclosure of Interests**: If the content creator or publisher holds interests in Microsoft, this should be disclosed to avoid conflicts of interest and ensure compliance with regulations such as the SEC’s rules on fair disclosure.\n", + "### Recommendations:\n", "\n", - "Recommendations:\n", + "- **Fact-Checking**: Verify all factual data through reliable stock market databases or financial reports released by Microsoft.\n", + " \n", + "- **Disclaimer**: Include a general disclaimer that the content is for informational purposes only and is not a form of financial advice or an inducement to invest.\n", "\n", - "- Ensure all factual claims are cross-verified with market data.\n", - "- If providing forward-looking statements, include the necessary disclaimers about market risks and the unpredictability of stock performance.\n", - "- Temper promotional language to provide a balanced viewpoint, avoiding the potential perception of investment advice.\n", - "- Avoid strong implications regarding investment decisions which are not supported by substantial evidence.\n", - "- Check if the content creator or publisher has any vested interest in Microsoft and ensure proper disclosures are included.\n", + "- **Balanced Analysis**: Emphasize the volatile nature of stock markets and integrate a risk-focused discussion to balance the positive sentiment conveyed.\n", "\n", - "Conclusion:\n", + "- **Neutral Language**: Adjust language to ensure neutrality and avoid any implications of advice or recommendation.\n", "\n", - "The content should satisfactorily meet compliance standards after careful verification of the facts, balancing language, and including necessary disclaimers and disclosures. The intention to avoid causing harm or misleading readers while ensuring fairness and compliance with regulations should guide any revisions.\n", + "In conclusion, while the content presents a positive overview of Microsoft's stock performance, it requires scrutiny for accuracy, neutrality, and conformance with regulatory standards, avoiding the potential of guiding market behavior or conveying investment advice. A careful revision ensuring these changes would align the material with the required guidelines and uphold the integrity of the content.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[34m\n", @@ -487,53 +496,63 @@ " On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.\n", "\n", "With the following carryover: \n", - "The Python code execution shows Microsoft stock (MSFT) exceeded $400 on 19 days from January 24, 2024, to February 21, 2024, with the highest closing price being $419.77 on February 9, 2024. The stock displayed periods of solid performance and stability above $400 during this timeframe, indicating positive market conditions or investor confidence in Microsoft's performance at the start of 2024.\n", - "Title: **Microsoft's Ascension: Stock Soars Above $400 in Early 2024**\n", + "Microsoft's stock (MSFT) closed above $400 on 21 separate days in 2024, demonstrating periods of strong stock performance with values reaching as high as around $420. The stock data suggests a bullish trend, especially in February when the stock peaked, indicating positive investor sentiment and robust market valuation during those times.\n", + "# Beyond the $400 Mark: Tracing Microsoft's Pinnacle Moments in 2024\n", + "\n", + "In the ever-fluctuating world of stocks, each milestone crossed can turn into a celebration or a stern reckoning of what the future holds. For Microsoft (MSFT), the year 2024 was a testament to its resilience and investor confidence as its stock ascended beyond the coveted $400 mark on 21 remarkable occasions.\n", + "\n", + "This performance wasn't just numerically significant; it was emblematic of a potent bullish energy within the market, affirming the tech giant’s leviathan presence amidst a highly competitive industry terrain. The loftiest point, flirting with the heights of approximately $420, served not just as a numerical peak, but as a beacon of the company’s market valuation and the unwavering investor trust in Microsoft's strategies and future prospects.\n", + "\n", + "## A Snapshot of Stellar Performance\n", + "\n", + "Microsoft's stock performance throughout 2024 was far from being a serendipitous spike. Instead, it showcased a carefully constructed narrative of growth and optimism. The chronicles of MSFT's venture past the $400 threshold are marked by fleeting dips and steadfast ascensions. Remarkably, the surge wasn't some scattergun pattern scattered across the calendar year. Rather, it was most pronounced in the month of February — a period that could very well be termed as the 'February Flourish.'\n", + "\n", + "During this short yet significant timeframe, investors witnessed the creation of incremental wealth that possibly outstripped all market expectations, setting a precedent for what a legacy company like Microsoft could achieve in a climate rife with disruptors and innovators.\n", "\n", - "In the upper echelons of the stock market, Microsoft Corporation (MSFT) stands as a gauge of innovative prowess and economic vitality. Its shares are more than just pieces of paper; they represent the confidence and aspirations of investors worldwide. It takes a remarkable convergence of market conditions and corporate triumphs for a stock to breach a milestone as significant as the $400 mark. Microsoft stock not only surpassed this threshold but did so with exceptional poise, ushering in a period of celebration for shareholders in the early months of 2024.\n", + "## Of Peaks and Valleys\n", "\n", - "Between January 24th and February 21st, Microsoft’s stock price proudly exceeded the $400 benchmark on 19 different occasions. This was not a fleeting brush with success but a testament to the tech giant’s enduring magnetism in the market. The crescendo of this symphony of gains was the stunning closing price of $419.77 on February 9th, marking the pinnacle of Microsoft’s financial performance during this period.\n", + "When assessing the grand canvas of Microsoft's stock performance, it is crucial to take a step back and appreciate the layered complexity of these financial movements. Amidst economic uncertainties and the technocratic whims that habitually serve both as tailwinds and headwinds to such companies, the story of Microsoft crossing the $400 threshold imbues the narrative with contours of investor euphoria interlaced with strategic agility.\n", "\n", - "As the graph of MSFT traced an enviable trajectory, market analysts and investors alike were reminded of the intrinsic strength bolstering Microsoft’s market position. This impressive trend was no accident; it was the result of a carefully balanced equation of innovation, strategic leadership, and market dynamics. Each day above $400 whispered a story of unwavering trust in Microsoft's trajectory and an underlying confidence in its ability to leverage opportunities, whether they lay in cloud computing, artificial intelligence, gaming, or productivity tools.\n", + "As these 21 days get etched into the fiscal annals of Microsoft, one cannot help but ponder the fine blend of factors — ranging from market dynamics to Microsoft's organizational maneuvers — that have coalesced to make these numbers a reality.\n", "\n", - "The persistent performance of MSFT not only indicated the vitality of Microsoft as a corporation but also reflected wider economic currents. In the dance of the stock market, every jump and dip is a response to the music played by investor sentiment, technological advancements, and global economic trends. To stay above $400 for a sustained period is to ride the crest of these forces, showcasing an alignment with the progress investors believe technology will undeniably drive in our everyday lives.\n", + "## A Convergence of Positive Sentiments\n", "\n", - "As we dissect these moments of triumph, we find the essence of Microsoft's financial fortitude. It's a blend of continuous innovation, well-calibrated risk management, and the company's intrinsic ability to evolve in the fast-paced high-tech arena. With products and services that have achieved a ubiquitous presence, Microsoft has been sculpting the future, byte by byte, in a world voraciously hungry for digital transformation.\n", + "What does the future hold for MSFT as it treads past this impressive benchmark? The takeaway is not a simple extrapolation of past performance. Rather, it's the realization that stock trajectories, particularly for a company like Microsoft, are a confluence of judicious corporate governance, product innovation, market anticipation, and, dare we say, a touch of the zeitgeist.\n", "\n", - "The million-dollar question remains: Can Microsoft maintain this stellar performance throughout 2024? While forecasts are invariably tinged with uncertainty, if the company's illustrious record of targeting burgeoning markets and expanding its influence is anything to go by, then anticipation is in the air. Investors watch with bated breath, pondering whether this IT titan will continue to climb or find this peak soon eclipsed by even higher summits.\n", + "Microsoft’s capacity to command such stock valitudes speaks volumes about its robust position in both the technology domain and the stock market ecosystem. Whether this fiscal feat can be sustained or surpassed remains a subject of much conjecture and anticipation.\n", "\n", - "As we turn the page on this chapter of Microsoft's fiscal saga, the only certainty is that the journey up the stock charts is as dynamic as the technologies the company masterfully crafts. Will the stock continue its flight, or will it face the gravity of market corrections? Only time will tell, but for now, Microsoft has given its investors reason to bask in the warm glow of market triumph.\n", - "As a compliance reviewer, my key focus in examining the provided content is to ensure that the material does not contain any misinformation, unsubstantiated claims, regulatory violations, or language that could be deemed harmful or misleading to consumers or investors. The review also includes a check for broader ethical considerations such as fairness, bias, and language appropriateness.\n", + "As we continue to navigate the ebbs and flows of an unpredictable market landscape, the days when Microsoft's stock rose gallantly above $400 will be recalled as milestones of 2024, celebrated reflections of a corporation sailing the high seas of market volatility with remarkable poise.\n", "\n", - "Review Summary:\n", + "The analysis of precise data points and enlightening interpretations reveal much about the past while providing distinctive glimpses of potential futures. With Microsoft standing tall amongst peers, its voyage above the $400 watermark will be chronicled as days of high tides in the annals of financial history.\n", + "The content provided serves as a market commentary, analyzing Microsoft's stock performance in the year 2024. In this review, I must ensure that the content is free from harmful elements, false claims, or regulatory violations.\n", "\n", - "1. **Accuracy of Information**: The content references specific dates and stock prices which are factual claims. These should be cross-verified with actual market data to ensure the information is correct. False or outdated information could mislead investors.\n", + "### Potential issues to consider:\n", "\n", - "2. **Investment Advice and Predictions**: The content should avoid making predictions or offering investment advice unless it is a regulated advisor with the appropriate disclaimers. Phrases like the \"million-dollar question\" and speculating on future performance need to be examined within the context of providing financial advice.\n", + "1. **Accuracy of Data**: Claims about stock prices and the number of days the stock closed above a certain threshold should be verifiable with stock market data. There must be confirmation that these numbers are accurate to present a truthful report and avoid misleading investors or stakeholders.\n", "\n", - "3. **Forward-looking Statements**: The content includes forward-looking statements about Microsoft's future performance. It's important to include a disclaimer that past performance is not indicative of future results, to ensure compliance with financial communication regulations.\n", + "2. **Forward-Looking Statements**: The article should avoid overly optimistic or pessimistic conjectures about future stock performance, which can be perceived as investment advice. Speculative statements should be clearly marked as such and provide a disclaimer that past performance does not guarantee future results.\n", "\n", - "4. **Promotional Language**: The title \"Microsoft's Ascension\" and descriptive phrases like \"ushering in a period of celebration for shareholders\" might be too promotional and could be seen as encouraging investment. The content should present a balanced view without overly promotional language.\n", + "3. **Fair and Balanced**: While the article largely celebrates Microsoft's performance, it should also acknowledge risks and uncertainties inherent in stock investments to maintain a fair and balanced approach without unduly influencing market sentiment.\n", "\n", - "5. **Balance and Fairness**: While the content praises Microsoft's performance, it should also fairly acknowledge market risks and realities without guaranteeing future results. Trust and confidence statements must be substantiated and not imply unrealistically high expectations.\n", + "4. **Non-Promotional Language**: Ensure that the content doesn't come off as promotional. Phrases like \"Microsoft's resilience and investor confidence\" and \"celebrated reflections of a corporation sailing the high seas of market volatility with remarkable poise\" should be reviewed to avoid appearing biased or overly promotional.\n", "\n", - "6. **Use of Metaphors and Language**: The content is rich with metaphors and expressive language (e.g., \"symphony of gains,\" \"dance of the stock market\"). While engaging, caution is warranted to ensure that the metaphors do not obscure facts, mislead, or overstate the stability and potential of an investment.\n", + "5. **Compliance with SEC Regulations**: In the United States, Securities and Exchange Commission (SEC) regulations require careful communication about stock performance to avoid market manipulation, insider trading implications, or misstatements.\n", "\n", - "7. **Market Manipulation**: There appears to be no intent to manipulate the market, but the optimistic tone could unduly influence investor decisions. A compliance reviewer should ensure the statements have basis and are not intended to artificially inflate stock value.\n", + "6. **Objective Tone**: While the narrative is engaging, it's important that it remains objective and does not use language that could be considered subjective or biased. Phrases like \"Microsoft’s leviathan presence\" or \"February Flourish\" might be interpreted as adding emotional color rather than stating facts.\n", "\n", - "8. **Disclosure of Interests**: If the content creator or publisher holds interests in Microsoft, this should be disclosed to avoid conflicts of interest and ensure compliance with regulations such as the SEC’s rules on fair disclosure.\n", + "7. **Clear Attribution**: If the report is based on external analyses or reported events, these should be clearly cited and attributed to maintain transparency.\n", "\n", - "Recommendations:\n", + "### Recommendations:\n", "\n", - "- Ensure all factual claims are cross-verified with market data.\n", - "- If providing forward-looking statements, include the necessary disclaimers about market risks and the unpredictability of stock performance.\n", - "- Temper promotional language to provide a balanced viewpoint, avoiding the potential perception of investment advice.\n", - "- Avoid strong implications regarding investment decisions which are not supported by substantial evidence.\n", - "- Check if the content creator or publisher has any vested interest in Microsoft and ensure proper disclosures are included.\n", + "- **Fact-Checking**: Verify all factual data through reliable stock market databases or financial reports released by Microsoft.\n", + " \n", + "- **Disclaimer**: Include a general disclaimer that the content is for informational purposes only and is not a form of financial advice or an inducement to invest.\n", "\n", - "Conclusion:\n", + "- **Balanced Analysis**: Emphasize the volatile nature of stock markets and integrate a risk-focused discussion to balance the positive sentiment conveyed.\n", "\n", - "The content should satisfactorily meet compliance standards after careful verification of the facts, balancing language, and including necessary disclaimers and disclosures. The intention to avoid causing harm or misleading readers while ensuring fairness and compliance with regulations should guide any revisions.\u001b[0m\n", + "- **Neutral Language**: Adjust language to ensure neutrality and avoid any implications of advice or recommendation.\n", + "\n", + "In conclusion, while the content presents a positive overview of Microsoft's stock performance, it requires scrutiny for accuracy, neutrality, and conformance with regulatory standards, avoiding the potential of guiding market behavior or conveying investment advice. A careful revision ensuring these changes would align the material with the required guidelines and uphold the integrity of the content.\u001b[0m\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", "\u001b[33mAssistant_1\u001b[0m (to Writer):\n", @@ -542,127 +561,121 @@ "\n", " On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.\n", "Context: \n", - "The Python code execution shows Microsoft stock (MSFT) exceeded $400 on 19 days from January 24, 2024, to February 21, 2024, with the highest closing price being $419.77 on February 9, 2024. The stock displayed periods of solid performance and stability above $400 during this timeframe, indicating positive market conditions or investor confidence in Microsoft's performance at the start of 2024.\n", - "Title: **Microsoft's Ascension: Stock Soars Above $400 in Early 2024**\n", - "\n", - "In the upper echelons of the stock market, Microsoft Corporation (MSFT) stands as a gauge of innovative prowess and economic vitality. Its shares are more than just pieces of paper; they represent the confidence and aspirations of investors worldwide. It takes a remarkable convergence of market conditions and corporate triumphs for a stock to breach a milestone as significant as the $400 mark. Microsoft stock not only surpassed this threshold but did so with exceptional poise, ushering in a period of celebration for shareholders in the early months of 2024.\n", - "\n", - "Between January 24th and February 21st, Microsoft’s stock price proudly exceeded the $400 benchmark on 19 different occasions. This was not a fleeting brush with success but a testament to the tech giant’s enduring magnetism in the market. The crescendo of this symphony of gains was the stunning closing price of $419.77 on February 9th, marking the pinnacle of Microsoft’s financial performance during this period.\n", + "Microsoft's stock (MSFT) closed above $400 on 21 separate days in 2024, demonstrating periods of strong stock performance with values reaching as high as around $420. The stock data suggests a bullish trend, especially in February when the stock peaked, indicating positive investor sentiment and robust market valuation during those times.\n", + "# Beyond the $400 Mark: Tracing Microsoft's Pinnacle Moments in 2024\n", "\n", - "As the graph of MSFT traced an enviable trajectory, market analysts and investors alike were reminded of the intrinsic strength bolstering Microsoft’s market position. This impressive trend was no accident; it was the result of a carefully balanced equation of innovation, strategic leadership, and market dynamics. Each day above $400 whispered a story of unwavering trust in Microsoft's trajectory and an underlying confidence in its ability to leverage opportunities, whether they lay in cloud computing, artificial intelligence, gaming, or productivity tools.\n", + "In the ever-fluctuating world of stocks, each milestone crossed can turn into a celebration or a stern reckoning of what the future holds. For Microsoft (MSFT), the year 2024 was a testament to its resilience and investor confidence as its stock ascended beyond the coveted $400 mark on 21 remarkable occasions.\n", "\n", - "The persistent performance of MSFT not only indicated the vitality of Microsoft as a corporation but also reflected wider economic currents. In the dance of the stock market, every jump and dip is a response to the music played by investor sentiment, technological advancements, and global economic trends. To stay above $400 for a sustained period is to ride the crest of these forces, showcasing an alignment with the progress investors believe technology will undeniably drive in our everyday lives.\n", + "This performance wasn't just numerically significant; it was emblematic of a potent bullish energy within the market, affirming the tech giant’s leviathan presence amidst a highly competitive industry terrain. The loftiest point, flirting with the heights of approximately $420, served not just as a numerical peak, but as a beacon of the company’s market valuation and the unwavering investor trust in Microsoft's strategies and future prospects.\n", "\n", - "As we dissect these moments of triumph, we find the essence of Microsoft's financial fortitude. It's a blend of continuous innovation, well-calibrated risk management, and the company's intrinsic ability to evolve in the fast-paced high-tech arena. With products and services that have achieved a ubiquitous presence, Microsoft has been sculpting the future, byte by byte, in a world voraciously hungry for digital transformation.\n", + "## A Snapshot of Stellar Performance\n", "\n", - "The million-dollar question remains: Can Microsoft maintain this stellar performance throughout 2024? While forecasts are invariably tinged with uncertainty, if the company's illustrious record of targeting burgeoning markets and expanding its influence is anything to go by, then anticipation is in the air. Investors watch with bated breath, pondering whether this IT titan will continue to climb or find this peak soon eclipsed by even higher summits.\n", + "Microsoft's stock performance throughout 2024 was far from being a serendipitous spike. Instead, it showcased a carefully constructed narrative of growth and optimism. The chronicles of MSFT's venture past the $400 threshold are marked by fleeting dips and steadfast ascensions. Remarkably, the surge wasn't some scattergun pattern scattered across the calendar year. Rather, it was most pronounced in the month of February — a period that could very well be termed as the 'February Flourish.'\n", "\n", - "As we turn the page on this chapter of Microsoft's fiscal saga, the only certainty is that the journey up the stock charts is as dynamic as the technologies the company masterfully crafts. Will the stock continue its flight, or will it face the gravity of market corrections? Only time will tell, but for now, Microsoft has given its investors reason to bask in the warm glow of market triumph.\n", - "As a compliance reviewer, my key focus in examining the provided content is to ensure that the material does not contain any misinformation, unsubstantiated claims, regulatory violations, or language that could be deemed harmful or misleading to consumers or investors. The review also includes a check for broader ethical considerations such as fairness, bias, and language appropriateness.\n", + "During this short yet significant timeframe, investors witnessed the creation of incremental wealth that possibly outstripped all market expectations, setting a precedent for what a legacy company like Microsoft could achieve in a climate rife with disruptors and innovators.\n", "\n", - "Review Summary:\n", + "## Of Peaks and Valleys\n", "\n", - "1. **Accuracy of Information**: The content references specific dates and stock prices which are factual claims. These should be cross-verified with actual market data to ensure the information is correct. False or outdated information could mislead investors.\n", + "When assessing the grand canvas of Microsoft's stock performance, it is crucial to take a step back and appreciate the layered complexity of these financial movements. Amidst economic uncertainties and the technocratic whims that habitually serve both as tailwinds and headwinds to such companies, the story of Microsoft crossing the $400 threshold imbues the narrative with contours of investor euphoria interlaced with strategic agility.\n", "\n", - "2. **Investment Advice and Predictions**: The content should avoid making predictions or offering investment advice unless it is a regulated advisor with the appropriate disclaimers. Phrases like the \"million-dollar question\" and speculating on future performance need to be examined within the context of providing financial advice.\n", + "As these 21 days get etched into the fiscal annals of Microsoft, one cannot help but ponder the fine blend of factors — ranging from market dynamics to Microsoft's organizational maneuvers — that have coalesced to make these numbers a reality.\n", "\n", - "3. **Forward-looking Statements**: The content includes forward-looking statements about Microsoft's future performance. It's important to include a disclaimer that past performance is not indicative of future results, to ensure compliance with financial communication regulations.\n", + "## A Convergence of Positive Sentiments\n", "\n", - "4. **Promotional Language**: The title \"Microsoft's Ascension\" and descriptive phrases like \"ushering in a period of celebration for shareholders\" might be too promotional and could be seen as encouraging investment. The content should present a balanced view without overly promotional language.\n", + "What does the future hold for MSFT as it treads past this impressive benchmark? The takeaway is not a simple extrapolation of past performance. Rather, it's the realization that stock trajectories, particularly for a company like Microsoft, are a confluence of judicious corporate governance, product innovation, market anticipation, and, dare we say, a touch of the zeitgeist.\n", "\n", - "5. **Balance and Fairness**: While the content praises Microsoft's performance, it should also fairly acknowledge market risks and realities without guaranteeing future results. Trust and confidence statements must be substantiated and not imply unrealistically high expectations.\n", + "Microsoft’s capacity to command such stock valitudes speaks volumes about its robust position in both the technology domain and the stock market ecosystem. Whether this fiscal feat can be sustained or surpassed remains a subject of much conjecture and anticipation.\n", "\n", - "6. **Use of Metaphors and Language**: The content is rich with metaphors and expressive language (e.g., \"symphony of gains,\" \"dance of the stock market\"). While engaging, caution is warranted to ensure that the metaphors do not obscure facts, mislead, or overstate the stability and potential of an investment.\n", + "As we continue to navigate the ebbs and flows of an unpredictable market landscape, the days when Microsoft's stock rose gallantly above $400 will be recalled as milestones of 2024, celebrated reflections of a corporation sailing the high seas of market volatility with remarkable poise.\n", "\n", - "7. **Market Manipulation**: There appears to be no intent to manipulate the market, but the optimistic tone could unduly influence investor decisions. A compliance reviewer should ensure the statements have basis and are not intended to artificially inflate stock value.\n", + "The analysis of precise data points and enlightening interpretations reveal much about the past while providing distinctive glimpses of potential futures. With Microsoft standing tall amongst peers, its voyage above the $400 watermark will be chronicled as days of high tides in the annals of financial history.\n", + "The content provided serves as a market commentary, analyzing Microsoft's stock performance in the year 2024. In this review, I must ensure that the content is free from harmful elements, false claims, or regulatory violations.\n", "\n", - "8. **Disclosure of Interests**: If the content creator or publisher holds interests in Microsoft, this should be disclosed to avoid conflicts of interest and ensure compliance with regulations such as the SEC’s rules on fair disclosure.\n", + "### Potential issues to consider:\n", "\n", - "Recommendations:\n", + "1. **Accuracy of Data**: Claims about stock prices and the number of days the stock closed above a certain threshold should be verifiable with stock market data. There must be confirmation that these numbers are accurate to present a truthful report and avoid misleading investors or stakeholders.\n", "\n", - "- Ensure all factual claims are cross-verified with market data.\n", - "- If providing forward-looking statements, include the necessary disclaimers about market risks and the unpredictability of stock performance.\n", - "- Temper promotional language to provide a balanced viewpoint, avoiding the potential perception of investment advice.\n", - "- Avoid strong implications regarding investment decisions which are not supported by substantial evidence.\n", - "- Check if the content creator or publisher has any vested interest in Microsoft and ensure proper disclosures are included.\n", + "2. **Forward-Looking Statements**: The article should avoid overly optimistic or pessimistic conjectures about future stock performance, which can be perceived as investment advice. Speculative statements should be clearly marked as such and provide a disclaimer that past performance does not guarantee future results.\n", "\n", - "Conclusion:\n", + "3. **Fair and Balanced**: While the article largely celebrates Microsoft's performance, it should also acknowledge risks and uncertainties inherent in stock investments to maintain a fair and balanced approach without unduly influencing market sentiment.\n", "\n", - "The content should satisfactorily meet compliance standards after careful verification of the facts, balancing language, and including necessary disclaimers and disclosures. The intention to avoid causing harm or misleading readers while ensuring fairness and compliance with regulations should guide any revisions.\n", + "4. **Non-Promotional Language**: Ensure that the content doesn't come off as promotional. Phrases like \"Microsoft's resilience and investor confidence\" and \"celebrated reflections of a corporation sailing the high seas of market volatility with remarkable poise\" should be reviewed to avoid appearing biased or overly promotional.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mWriter\u001b[0m (to Assistant_1):\n", + "5. **Compliance with SEC Regulations**: In the United States, Securities and Exchange Commission (SEC) regulations require careful communication about stock performance to avoid market manipulation, insider trading implications, or misstatements.\n", "\n", - "**Microsoft's Ascension: Stock Soars Above $400 in Early 2024**\n", + "6. **Objective Tone**: While the narrative is engaging, it's important that it remains objective and does not use language that could be considered subjective or biased. Phrases like \"Microsoft’s leviathan presence\" or \"February Flourish\" might be interpreted as adding emotional color rather than stating facts.\n", "\n", - "In the grand tapestry of the stock market, certain companies stand out as beacons of innovation and economic growth. Microsoft Corporation (MSFT) is one such luminary, with shares that symbolize the pulse of investor confidence and the future of technology. It's a rare and noteworthy occasion when a company's stock surpasses a significant milestone, and in early 2024, Microsoft's stock achieved just that, gracefully ascending beyond the $400 threshold and capturing the attention of the investment world.\n", + "7. **Clear Attribution**: If the report is based on external analyses or reported events, these should be clearly cited and attributed to maintain transparency.\n", "\n", - "During a remarkable period from January 24th to February 21st, 2024, Microsoft's equity commanded a price greater than $400 on 19 distinct days. This wasn't a fleeting moment of success but a sustained display of the tech titan's allure in the market, with the apex of achievement being a closing price of $419.77 on February 9th—a high watermark for Microsoft's fiscal narrative in this span.\n", + "### Recommendations:\n", "\n", - "Such a consistent performance from MSFT underscores a robust corporate backbone, reinforcing the idea that Microsoft's strategic maneuvers and product innovations resonate positively within the investor community. In this stretch, the company didn't merely flirt with success; it maintained a courtship with excellence, predicated on a rich history of seizing emergent trends and opportunities in domains like cloud services, AI, gaming, and ubiquitous productivity platforms.\n", + "- **Fact-Checking**: Verify all factual data through reliable stock market databases or financial reports released by Microsoft.\n", + " \n", + "- **Disclaimer**: Include a general disclaimer that the content is for informational purposes only and is not a form of financial advice or an inducement to invest.\n", "\n", - "The stock's watershed moments above $400 reflect not only the solid foundations of Microsoft as a global tech leader but also a broader economic context where each high and low is choreographed by the rhythms of market sentiment, technology breakthroughs, and overarching economic forces. A consistent price point above $400 is like surfing the crest of these tides, a demonstration of alignment with the investor vision that technology is the driving force in our rapidly evolving digital existence.\n", + "- **Balanced Analysis**: Emphasize the volatile nature of stock markets and integrate a risk-focused discussion to balance the positive sentiment conveyed.\n", "\n", - "To unravel the threads of Microsoft's financial resilience is to uncover a tapestry woven with innovation, astute risk management, and an evolutionary agility in the bustling tech arena. With a repertoire ranging from essential software to trailblazing cloud infrastructure, Microsoft has been steadily carving out the future, shaping the digital landscape with an insatiable demand for progress.\n", + "- **Neutral Language**: Adjust language to ensure neutrality and avoid any implications of advice or recommendation.\n", "\n", - "As anticipatory gazes turn towards the remainder of 2024, queries hover around Microsoft's ability to maintain its elevated fiscal flight path. Although the landscape of the future is painted with uncertainty, the tech giant’s previous mastery in capturing the zeitgeist of digital enterprise gives rise to optimistic speculation. Investors, with keen eyes and hopeful hearts, muse on whether this illustrious chapter will extend or if even loftier heights await Microsoft's stock.\n", + "In conclusion, while the content presents a positive overview of Microsoft's stock performance, it requires scrutiny for accuracy, neutrality, and conformance with regulatory standards, avoiding the potential of guiding market behavior or conveying investment advice. A careful revision ensuring these changes would align the material with the required guidelines and uphold the integrity of the content.\n", "\n", - "In closing the book on this current section of Microsoft's economic journey, the one ascertainable truth lies in the dynamic nature of the stock's ascent—a story as fluid and electrifying as the very technologies the company expertly deploys. Whether MSFT will continue its ascent or encounter the earthly pull of market adjustments remains to be seen. For now, however, Microsoft's stakeholders have ample reasons to revel in their company's robust market surge.\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mWriter\u001b[0m (to Assistant_1):\n", "\n", - "**Compliance Assessment: Positioning Microsoft's Stock Performance with Prudence**\n", + "# Beyond the $400 Mark: Tracing Microsoft's Pinnacle Moments in 2024\n", "\n", - "Given the importance of accuracy and ethics in financial content, this blog post has been crafted to adhere to informational integrity and regulatory sensitivity. While the content presents an upbeat view of Microsoft's stock performance, it is important to note the following:\n", + "Microsoft (MSFT) surpassed a notable milestone in the stock market in 2024, a testament to its strength and the trust placed in it by investors. On a triumphant 21 days, the tech behemoth's share price closed above the $400 benchmark, with a zenith teetering near $420, demonstrating not just robust performance but also the confidence that the market has in Microsoft's future.\n", "\n", - "1. **Verification of Data**: Any claims regarding dates and stock prices should be independently cross-checked against reliable financial databases to affirm their accuracy.\n", + "## A Year of Impressive Highs\n", "\n", - "2. **Foresight and Advisement**: Forward-looking statements are inherently speculative and should be accompanied by appropriate disclaimers that past performance is not predictive of future results, and readers should exercise due diligence.\n", + "The journey of MSFT’s stock transcending the $400 mark is a narrative of strategic growth and optimism, not merely random fluctuations. These moments are a constellation of careful planning, ambition, and market forces. A standout period in this impressive ascent was in February, aptly dubbed as the 'February Flourish', where Microsoft stock not only soared but also set the precedent for the company’s continuous evolution in a market brimming with innovation.\n", "\n", - "3. **Promotional Restraint**: It is crucial to strike a balance between engaging narrative and promotional verbiage. The goal is to inform, not to influence unduly. Where positive performance is highlighted, context is given to ground expectations in reality.\n", + "## Peaks, Valleys, and What They Portend\n", "\n", - "4. **Market Ethos**: Use of creative metaphors and expressive language is to enhance reader engagement—yet caution is observed to ensure that artistic flair does not obscure factual representation or imply infallible investment moves.\n", + "It's imperative to consider the intricacies behind the ascension of Microsoft's shares. In the constantly shifting sands of the global economy and tech innovation, the recurrence of MSFT above $400 illustrates more than investor euphoria; it reflects the company’s agility in a changing world.\n", "\n", - "5. **Public Servitude**: A rigorous stance against market manipulation is upheld, ensuring that any optimism portrayed is founded on reportable events and not intended to spur unwarranted investor action.\n", + "Given these high points, it’s natural to wonder about Microsoft's fiscal trajectory. It goes without saying that navigating stock market trends is a complex endeavor. Microsoft’s history, however, shows a confluence of strategic achievements and a pulse on market needs.\n", "\n", - "6. **Transparency**: There are no vested interests held by the author in Microsoft, ensuring unbiased analysis and a clear presentation without conflicts of interest.\n", + "## A Fusion of Market Dynamics\n", "\n", - "Should any factual discrepancies arise, they will be duly rectified to honor the trust placed in responsible financial communication. It is the ongoing commitment to fuse the realms of financial insight with narrative finesse while navigating within a framework of regulatory standards and ethical practice.\n", + "Will MSFT maintain its stronghold in the market or even exceed these past achievements? It's not just about projecting trends, but understanding the undercurrents. The factors at play involve Microsoft's corporate governance, innovative drive, and their resonance with the zeitgeist.\n", "\n", - "--------------------------------------------------------------------------------\n", - "\u001b[33mAssistant_1\u001b[0m (to User):\n", + "Microsoft's standing in the market is a story of overcoming volatility with grace—and these key days when it climbed past the $400 barrier will be commemorated as significant moments in 2024.\n", "\n", - "**Microsoft's Ascension: Stock Soars Above $400 in Early 2024**\n", + "While this analysis provides a snapshot of Microsoft’s strides in the stock market, it is important to remind readers that this content is solely for informational purposes, and should not be construed as financial advice. Data accuracy, balance in discussion, compliance with regulatory standards, and the maintaining of an objective tone are essential to provide a credible narrative that respects the complexity of market movements and investor decisions.\n", "\n", - "In the grand tapestry of the stock market, certain companies stand out as beacons of innovation and economic growth. Microsoft Corporation (MSFT) is one such luminary, with shares that symbolize the pulse of investor confidence and the future of technology. It's a rare and noteworthy occasion when a company's stock surpasses a significant milestone, and in early 2024, Microsoft's stock achieved just that, gracefully ascending beyond the $400 threshold and capturing the attention of the investment world.\n", + "*As always, please remember that past performance is not indicative of future results, and this article should not be taken as an invitation to invest. Stocks can fluctuate, and investments carry risks that should be carefully evaluated.*\n", "\n", - "During a remarkable period from January 24th to February 21st, 2024, Microsoft's equity commanded a price greater than $400 on 19 distinct days. This wasn't a fleeting moment of success but a sustained display of the tech titan's allure in the market, with the apex of achievement being a closing price of $419.77 on February 9th—a high watermark for Microsoft's fiscal narrative in this span.\n", + "In sum, while the content offers an optimistic perspective of Microsoft's stock performance, it requires close consideration for authenticity, regulatory compliance, and to ensure it serves as an informative piece rather than guiding market behavior or offering investment counsel. A meticulous revision, embodying these principles, would elevate the piece to meet professional and ethical standards, ensuring the integrity of the content remains intact.\n", "\n", - "Such a consistent performance from MSFT underscores a robust corporate backbone, reinforcing the idea that Microsoft's strategic maneuvers and product innovations resonate positively within the investor community. In this stretch, the company didn't merely flirt with success; it maintained a courtship with excellence, predicated on a rich history of seizing emergent trends and opportunities in domains like cloud services, AI, gaming, and ubiquitous productivity platforms.\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mAssistant_1\u001b[0m (to User):\n", "\n", - "The stock's watershed moments above $400 reflect not only the solid foundations of Microsoft as a global tech leader but also a broader economic context where each high and low is choreographed by the rhythms of market sentiment, technology breakthroughs, and overarching economic forces. A consistent price point above $400 is like surfing the crest of these tides, a demonstration of alignment with the investor vision that technology is the driving force in our rapidly evolving digital existence.\n", + "# Beyond the $400 Mark: Tracing Microsoft's Pinnacle Moments in 2024\n", "\n", - "To unravel the threads of Microsoft's financial resilience is to uncover a tapestry woven with innovation, astute risk management, and an evolutionary agility in the bustling tech arena. With a repertoire ranging from essential software to trailblazing cloud infrastructure, Microsoft has been steadily carving out the future, shaping the digital landscape with an insatiable demand for progress.\n", + "Microsoft (MSFT) surpassed a notable milestone in the stock market in 2024, a testament to its strength and the trust placed in it by investors. On a triumphant 21 days, the tech behemoth's share price closed above the $400 benchmark, with a zenith teetering near $420, demonstrating not just robust performance but also the confidence that the market has in Microsoft's future.\n", "\n", - "As anticipatory gazes turn towards the remainder of 2024, queries hover around Microsoft's ability to maintain its elevated fiscal flight path. Although the landscape of the future is painted with uncertainty, the tech giant’s previous mastery in capturing the zeitgeist of digital enterprise gives rise to optimistic speculation. Investors, with keen eyes and hopeful hearts, muse on whether this illustrious chapter will extend or if even loftier heights await Microsoft's stock.\n", + "## A Year of Impressive Highs\n", "\n", - "In closing the book on this current section of Microsoft's economic journey, the one ascertainable truth lies in the dynamic nature of the stock's ascent—a story as fluid and electrifying as the very technologies the company expertly deploys. Whether MSFT will continue its ascent or encounter the earthly pull of market adjustments remains to be seen. For now, however, Microsoft's stakeholders have ample reasons to revel in their company's robust market surge.\n", + "The journey of MSFT’s stock transcending the $400 mark is a narrative of strategic growth and optimism, not merely random fluctuations. These moments are a constellation of careful planning, ambition, and market forces. A standout period in this impressive ascent was in February, aptly dubbed as the 'February Flourish', where Microsoft stock not only soared but also set the precedent for the company’s continuous evolution in a market brimming with innovation.\n", "\n", - "**Compliance Assessment: Positioning Microsoft's Stock Performance with Prudence**\n", + "## Peaks, Valleys, and What They Portend\n", "\n", - "Given the importance of accuracy and ethics in financial content, this blog post has been crafted to adhere to informational integrity and regulatory sensitivity. While the content presents an upbeat view of Microsoft's stock performance, it is important to note the following:\n", + "It's imperative to consider the intricacies behind the ascension of Microsoft's shares. In the constantly shifting sands of the global economy and tech innovation, the recurrence of MSFT above $400 illustrates more than investor euphoria; it reflects the company’s agility in a changing world.\n", "\n", - "1. **Verification of Data**: Any claims regarding dates and stock prices should be independently cross-checked against reliable financial databases to affirm their accuracy.\n", + "Given these high points, it’s natural to wonder about Microsoft's fiscal trajectory. It goes without saying that navigating stock market trends is a complex endeavor. Microsoft’s history, however, shows a confluence of strategic achievements and a pulse on market needs.\n", "\n", - "2. **Foresight and Advisement**: Forward-looking statements are inherently speculative and should be accompanied by appropriate disclaimers that past performance is not predictive of future results, and readers should exercise due diligence.\n", + "## A Fusion of Market Dynamics\n", "\n", - "3. **Promotional Restraint**: It is crucial to strike a balance between engaging narrative and promotional verbiage. The goal is to inform, not to influence unduly. Where positive performance is highlighted, context is given to ground expectations in reality.\n", + "Will MSFT maintain its stronghold in the market or even exceed these past achievements? It's not just about projecting trends, but understanding the undercurrents. The factors at play involve Microsoft's corporate governance, innovative drive, and their resonance with the zeitgeist.\n", "\n", - "4. **Market Ethos**: Use of creative metaphors and expressive language is to enhance reader engagement—yet caution is observed to ensure that artistic flair does not obscure factual representation or imply infallible investment moves.\n", + "Microsoft's standing in the market is a story of overcoming volatility with grace—and these key days when it climbed past the $400 barrier will be commemorated as significant moments in 2024.\n", "\n", - "5. **Public Servitude**: A rigorous stance against market manipulation is upheld, ensuring that any optimism portrayed is founded on reportable events and not intended to spur unwarranted investor action.\n", + "While this analysis provides a snapshot of Microsoft’s strides in the stock market, it is important to remind readers that this content is solely for informational purposes, and should not be construed as financial advice. Data accuracy, balance in discussion, compliance with regulatory standards, and the maintaining of an objective tone are essential to provide a credible narrative that respects the complexity of market movements and investor decisions.\n", "\n", - "6. **Transparency**: There are no vested interests held by the author in Microsoft, ensuring unbiased analysis and a clear presentation without conflicts of interest.\n", + "*As always, please remember that past performance is not indicative of future results, and this article should not be taken as an invitation to invest. Stocks can fluctuate, and investments carry risks that should be carefully evaluated.*\n", "\n", - "Should any factual discrepancies arise, they will be duly rectified to honor the trust placed in responsible financial communication. It is the ongoing commitment to fuse the realms of financial insight with narrative finesse while navigating within a framework of regulatory standards and ethical practice.\n", + "In sum, while the content offers an optimistic perspective of Microsoft's stock performance, it requires close consideration for authenticity, regulatory compliance, and to ensure it serves as an informative piece rather than guiding market behavior or offering investment counsel. A meticulous revision, embodying these principles, would elevate the piece to meet professional and ethical standards, ensuring the integrity of the content remains intact.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[34m\n", @@ -671,85 +684,69 @@ "Make a pleasant joke about it.\n", "\n", "With the following carryover: \n", - "**Microsoft's Ascension: Stock Soars Above $400 in Early 2024**\n", - "\n", - "In the grand tapestry of the stock market, certain companies stand out as beacons of innovation and economic growth. Microsoft Corporation (MSFT) is one such luminary, with shares that symbolize the pulse of investor confidence and the future of technology. It's a rare and noteworthy occasion when a company's stock surpasses a significant milestone, and in early 2024, Microsoft's stock achieved just that, gracefully ascending beyond the $400 threshold and capturing the attention of the investment world.\n", - "\n", - "During a remarkable period from January 24th to February 21st, 2024, Microsoft's equity commanded a price greater than $400 on 19 distinct days. This wasn't a fleeting moment of success but a sustained display of the tech titan's allure in the market, with the apex of achievement being a closing price of $419.77 on February 9th—a high watermark for Microsoft's fiscal narrative in this span.\n", - "\n", - "Such a consistent performance from MSFT underscores a robust corporate backbone, reinforcing the idea that Microsoft's strategic maneuvers and product innovations resonate positively within the investor community. In this stretch, the company didn't merely flirt with success; it maintained a courtship with excellence, predicated on a rich history of seizing emergent trends and opportunities in domains like cloud services, AI, gaming, and ubiquitous productivity platforms.\n", - "\n", - "The stock's watershed moments above $400 reflect not only the solid foundations of Microsoft as a global tech leader but also a broader economic context where each high and low is choreographed by the rhythms of market sentiment, technology breakthroughs, and overarching economic forces. A consistent price point above $400 is like surfing the crest of these tides, a demonstration of alignment with the investor vision that technology is the driving force in our rapidly evolving digital existence.\n", + "# Beyond the $400 Mark: Tracing Microsoft's Pinnacle Moments in 2024\n", "\n", - "To unravel the threads of Microsoft's financial resilience is to uncover a tapestry woven with innovation, astute risk management, and an evolutionary agility in the bustling tech arena. With a repertoire ranging from essential software to trailblazing cloud infrastructure, Microsoft has been steadily carving out the future, shaping the digital landscape with an insatiable demand for progress.\n", + "Microsoft (MSFT) surpassed a notable milestone in the stock market in 2024, a testament to its strength and the trust placed in it by investors. On a triumphant 21 days, the tech behemoth's share price closed above the $400 benchmark, with a zenith teetering near $420, demonstrating not just robust performance but also the confidence that the market has in Microsoft's future.\n", "\n", - "As anticipatory gazes turn towards the remainder of 2024, queries hover around Microsoft's ability to maintain its elevated fiscal flight path. Although the landscape of the future is painted with uncertainty, the tech giant’s previous mastery in capturing the zeitgeist of digital enterprise gives rise to optimistic speculation. Investors, with keen eyes and hopeful hearts, muse on whether this illustrious chapter will extend or if even loftier heights await Microsoft's stock.\n", + "## A Year of Impressive Highs\n", "\n", - "In closing the book on this current section of Microsoft's economic journey, the one ascertainable truth lies in the dynamic nature of the stock's ascent—a story as fluid and electrifying as the very technologies the company expertly deploys. Whether MSFT will continue its ascent or encounter the earthly pull of market adjustments remains to be seen. For now, however, Microsoft's stakeholders have ample reasons to revel in their company's robust market surge.\n", + "The journey of MSFT’s stock transcending the $400 mark is a narrative of strategic growth and optimism, not merely random fluctuations. These moments are a constellation of careful planning, ambition, and market forces. A standout period in this impressive ascent was in February, aptly dubbed as the 'February Flourish', where Microsoft stock not only soared but also set the precedent for the company’s continuous evolution in a market brimming with innovation.\n", "\n", - "**Compliance Assessment: Positioning Microsoft's Stock Performance with Prudence**\n", + "## Peaks, Valleys, and What They Portend\n", "\n", - "Given the importance of accuracy and ethics in financial content, this blog post has been crafted to adhere to informational integrity and regulatory sensitivity. While the content presents an upbeat view of Microsoft's stock performance, it is important to note the following:\n", + "It's imperative to consider the intricacies behind the ascension of Microsoft's shares. In the constantly shifting sands of the global economy and tech innovation, the recurrence of MSFT above $400 illustrates more than investor euphoria; it reflects the company’s agility in a changing world.\n", "\n", - "1. **Verification of Data**: Any claims regarding dates and stock prices should be independently cross-checked against reliable financial databases to affirm their accuracy.\n", + "Given these high points, it’s natural to wonder about Microsoft's fiscal trajectory. It goes without saying that navigating stock market trends is a complex endeavor. Microsoft’s history, however, shows a confluence of strategic achievements and a pulse on market needs.\n", "\n", - "2. **Foresight and Advisement**: Forward-looking statements are inherently speculative and should be accompanied by appropriate disclaimers that past performance is not predictive of future results, and readers should exercise due diligence.\n", + "## A Fusion of Market Dynamics\n", "\n", - "3. **Promotional Restraint**: It is crucial to strike a balance between engaging narrative and promotional verbiage. The goal is to inform, not to influence unduly. Where positive performance is highlighted, context is given to ground expectations in reality.\n", + "Will MSFT maintain its stronghold in the market or even exceed these past achievements? It's not just about projecting trends, but understanding the undercurrents. The factors at play involve Microsoft's corporate governance, innovative drive, and their resonance with the zeitgeist.\n", "\n", - "4. **Market Ethos**: Use of creative metaphors and expressive language is to enhance reader engagement—yet caution is observed to ensure that artistic flair does not obscure factual representation or imply infallible investment moves.\n", + "Microsoft's standing in the market is a story of overcoming volatility with grace—and these key days when it climbed past the $400 barrier will be commemorated as significant moments in 2024.\n", "\n", - "5. **Public Servitude**: A rigorous stance against market manipulation is upheld, ensuring that any optimism portrayed is founded on reportable events and not intended to spur unwarranted investor action.\n", + "While this analysis provides a snapshot of Microsoft’s strides in the stock market, it is important to remind readers that this content is solely for informational purposes, and should not be construed as financial advice. Data accuracy, balance in discussion, compliance with regulatory standards, and the maintaining of an objective tone are essential to provide a credible narrative that respects the complexity of market movements and investor decisions.\n", "\n", - "6. **Transparency**: There are no vested interests held by the author in Microsoft, ensuring unbiased analysis and a clear presentation without conflicts of interest.\n", + "*As always, please remember that past performance is not indicative of future results, and this article should not be taken as an invitation to invest. Stocks can fluctuate, and investments carry risks that should be carefully evaluated.*\n", "\n", - "Should any factual discrepancies arise, they will be duly rectified to honor the trust placed in responsible financial communication. It is the ongoing commitment to fuse the realms of financial insight with narrative finesse while navigating within a framework of regulatory standards and ethical practice.\u001b[0m\n", + "In sum, while the content offers an optimistic perspective of Microsoft's stock performance, it requires close consideration for authenticity, regulatory compliance, and to ensure it serves as an informative piece rather than guiding market behavior or offering investment counsel. A meticulous revision, embodying these principles, would elevate the piece to meet professional and ethical standards, ensuring the integrity of the content remains intact.\u001b[0m\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", "\u001b[33mUser\u001b[0m (to Assistant_2):\n", "\n", "Make a pleasant joke about it.\n", "Context: \n", - "**Microsoft's Ascension: Stock Soars Above $400 in Early 2024**\n", - "\n", - "In the grand tapestry of the stock market, certain companies stand out as beacons of innovation and economic growth. Microsoft Corporation (MSFT) is one such luminary, with shares that symbolize the pulse of investor confidence and the future of technology. It's a rare and noteworthy occasion when a company's stock surpasses a significant milestone, and in early 2024, Microsoft's stock achieved just that, gracefully ascending beyond the $400 threshold and capturing the attention of the investment world.\n", - "\n", - "During a remarkable period from January 24th to February 21st, 2024, Microsoft's equity commanded a price greater than $400 on 19 distinct days. This wasn't a fleeting moment of success but a sustained display of the tech titan's allure in the market, with the apex of achievement being a closing price of $419.77 on February 9th—a high watermark for Microsoft's fiscal narrative in this span.\n", - "\n", - "Such a consistent performance from MSFT underscores a robust corporate backbone, reinforcing the idea that Microsoft's strategic maneuvers and product innovations resonate positively within the investor community. In this stretch, the company didn't merely flirt with success; it maintained a courtship with excellence, predicated on a rich history of seizing emergent trends and opportunities in domains like cloud services, AI, gaming, and ubiquitous productivity platforms.\n", - "\n", - "The stock's watershed moments above $400 reflect not only the solid foundations of Microsoft as a global tech leader but also a broader economic context where each high and low is choreographed by the rhythms of market sentiment, technology breakthroughs, and overarching economic forces. A consistent price point above $400 is like surfing the crest of these tides, a demonstration of alignment with the investor vision that technology is the driving force in our rapidly evolving digital existence.\n", + "# Beyond the $400 Mark: Tracing Microsoft's Pinnacle Moments in 2024\n", "\n", - "To unravel the threads of Microsoft's financial resilience is to uncover a tapestry woven with innovation, astute risk management, and an evolutionary agility in the bustling tech arena. With a repertoire ranging from essential software to trailblazing cloud infrastructure, Microsoft has been steadily carving out the future, shaping the digital landscape with an insatiable demand for progress.\n", + "Microsoft (MSFT) surpassed a notable milestone in the stock market in 2024, a testament to its strength and the trust placed in it by investors. On a triumphant 21 days, the tech behemoth's share price closed above the $400 benchmark, with a zenith teetering near $420, demonstrating not just robust performance but also the confidence that the market has in Microsoft's future.\n", "\n", - "As anticipatory gazes turn towards the remainder of 2024, queries hover around Microsoft's ability to maintain its elevated fiscal flight path. Although the landscape of the future is painted with uncertainty, the tech giant’s previous mastery in capturing the zeitgeist of digital enterprise gives rise to optimistic speculation. Investors, with keen eyes and hopeful hearts, muse on whether this illustrious chapter will extend or if even loftier heights await Microsoft's stock.\n", + "## A Year of Impressive Highs\n", "\n", - "In closing the book on this current section of Microsoft's economic journey, the one ascertainable truth lies in the dynamic nature of the stock's ascent—a story as fluid and electrifying as the very technologies the company expertly deploys. Whether MSFT will continue its ascent or encounter the earthly pull of market adjustments remains to be seen. For now, however, Microsoft's stakeholders have ample reasons to revel in their company's robust market surge.\n", + "The journey of MSFT’s stock transcending the $400 mark is a narrative of strategic growth and optimism, not merely random fluctuations. These moments are a constellation of careful planning, ambition, and market forces. A standout period in this impressive ascent was in February, aptly dubbed as the 'February Flourish', where Microsoft stock not only soared but also set the precedent for the company’s continuous evolution in a market brimming with innovation.\n", "\n", - "**Compliance Assessment: Positioning Microsoft's Stock Performance with Prudence**\n", + "## Peaks, Valleys, and What They Portend\n", "\n", - "Given the importance of accuracy and ethics in financial content, this blog post has been crafted to adhere to informational integrity and regulatory sensitivity. While the content presents an upbeat view of Microsoft's stock performance, it is important to note the following:\n", + "It's imperative to consider the intricacies behind the ascension of Microsoft's shares. In the constantly shifting sands of the global economy and tech innovation, the recurrence of MSFT above $400 illustrates more than investor euphoria; it reflects the company’s agility in a changing world.\n", "\n", - "1. **Verification of Data**: Any claims regarding dates and stock prices should be independently cross-checked against reliable financial databases to affirm their accuracy.\n", + "Given these high points, it’s natural to wonder about Microsoft's fiscal trajectory. It goes without saying that navigating stock market trends is a complex endeavor. Microsoft’s history, however, shows a confluence of strategic achievements and a pulse on market needs.\n", "\n", - "2. **Foresight and Advisement**: Forward-looking statements are inherently speculative and should be accompanied by appropriate disclaimers that past performance is not predictive of future results, and readers should exercise due diligence.\n", + "## A Fusion of Market Dynamics\n", "\n", - "3. **Promotional Restraint**: It is crucial to strike a balance between engaging narrative and promotional verbiage. The goal is to inform, not to influence unduly. Where positive performance is highlighted, context is given to ground expectations in reality.\n", + "Will MSFT maintain its stronghold in the market or even exceed these past achievements? It's not just about projecting trends, but understanding the undercurrents. The factors at play involve Microsoft's corporate governance, innovative drive, and their resonance with the zeitgeist.\n", "\n", - "4. **Market Ethos**: Use of creative metaphors and expressive language is to enhance reader engagement—yet caution is observed to ensure that artistic flair does not obscure factual representation or imply infallible investment moves.\n", + "Microsoft's standing in the market is a story of overcoming volatility with grace—and these key days when it climbed past the $400 barrier will be commemorated as significant moments in 2024.\n", "\n", - "5. **Public Servitude**: A rigorous stance against market manipulation is upheld, ensuring that any optimism portrayed is founded on reportable events and not intended to spur unwarranted investor action.\n", + "While this analysis provides a snapshot of Microsoft’s strides in the stock market, it is important to remind readers that this content is solely for informational purposes, and should not be construed as financial advice. Data accuracy, balance in discussion, compliance with regulatory standards, and the maintaining of an objective tone are essential to provide a credible narrative that respects the complexity of market movements and investor decisions.\n", "\n", - "6. **Transparency**: There are no vested interests held by the author in Microsoft, ensuring unbiased analysis and a clear presentation without conflicts of interest.\n", + "*As always, please remember that past performance is not indicative of future results, and this article should not be taken as an invitation to invest. Stocks can fluctuate, and investments carry risks that should be carefully evaluated.*\n", "\n", - "Should any factual discrepancies arise, they will be duly rectified to honor the trust placed in responsible financial communication. It is the ongoing commitment to fuse the realms of financial insight with narrative finesse while navigating within a framework of regulatory standards and ethical practice.\n", + "In sum, while the content offers an optimistic perspective of Microsoft's stock performance, it requires close consideration for authenticity, regulatory compliance, and to ensure it serves as an informative piece rather than guiding market behavior or offering investment counsel. A meticulous revision, embodying these principles, would elevate the piece to meet professional and ethical standards, ensuring the integrity of the content remains intact.\n", "\n", "--------------------------------------------------------------------------------\n", "\u001b[33mAssistant_2\u001b[0m (to User):\n", "\n", - "It seems like Microsoft has really upped its game—now, not only is its software running on computers everywhere, but its stock is also running laps around Wall Street! You know you're doing well when your stock price is climbing faster than a software update on a brand-new machine. Quick, someone make sure their stock ticker isn’t just a looping animated GIF – it’s heading up so high, it’s practically in the cloud!\n", + "Well, it seems Microsoft's stock price isn't the only thing on a high - it must have taken a leaf out of Elon Musk's book, aiming for that 420 mark! Just remember, unlike Microsoft's shares, not all jokes appreciate over time – especially the ones about the stock market! \n", "\n", - "Now, let's make sure to have our \"stocks\" and \"software\" in order, remember to update regularly, and don't take this surge as a \"default setting\" for future performance. After all, if stocks were as predictable as a pop-up reminder, we'd all be clicking \"snooze\" on retirement plans. 😄\n", + "On a serious note, jokes about financial achievements should be handled with care, as the subject of investments can significantly impact individuals' lives. However, lighthearted humor, when appropriate, can make an informative piece more engaging! \n", "\n", "TERMINATE\n", "\n", From bfe9e3405716081800f704dcccbe2fe843f2805e Mon Sep 17 00:00:00 2001 From: signalprime <15487280+signalprime@users.noreply.github.com> Date: Sat, 24 Feb 2024 17:45:34 -0600 Subject: [PATCH 42/57] Expanded details about the purpose of each cell --- notebook/agentchat_nestedchat_optiguide.ipynb | 91 +++++++++++++++++-- 1 file changed, 85 insertions(+), 6 deletions(-) diff --git a/notebook/agentchat_nestedchat_optiguide.ipynb b/notebook/agentchat_nestedchat_optiguide.ipynb index b75e55cfae6..cb7c461b708 100644 --- a/notebook/agentchat_nestedchat_optiguide.ipynb +++ b/notebook/agentchat_nestedchat_optiguide.ipynb @@ -16,6 +16,12 @@ "\n", "## Requirements\n", "\n", + "This notebook requires eventlet and Gurobipy. The eventlet package is used in this notebook to constrain code execution with a timeout, and the gurobipy package is a mathematical optimization software library for solving mixed-integer linear and quadratic optimization problems.\n", + "They can be installed together by running:\n", + "```bash\n", + "pip install eventlet gurobipy\n", + "```\n", + "\n", "AutoGen requires `Python>=3.8`. To run this notebook example, please install:\n", "```bash\n", "pip install pyautogen\n", @@ -51,18 +57,42 @@ ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ - "## OptiGuide with Nested Chat" + "# OptiGuide with Nested Chat" ] }, { + "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ - "### System Messages" + "## Cell Explanation:\n", + "We set up the foundation for a supply chain coding project, integrating Python code solutions with Gurobi, a popular optimization solver. It accomplishes this through a combination of system messages, utility functions, and code retrieval from an external source. This allows the chatbot to assist users in solving supply chain optimization problems by generating, evaluating, and refining Python code with the Gurobi solver.\n", + "\n", + "### System Messages\n", + "The cell defines several template strings (**WRITER_SYSTEM_MSG**, **SAFEGUARD_SYSTEM_MSG**, etc.) used to guide the interaction between the chatbot (you) and the user. These messages serve multiple purposes:\n", + "\n", + "- **WRITER_SYSTEM_MSG:**
Instructs the chatbot on how to generate and explain Python code solutions for supply chain-related questions, including where to insert the generated code.\n", + "\n", + "- **SAFEGUARD_SYSTEM_MSG:**
Asks the chatbot to determine whether the provided source code is safe to execute, helping to ensure security and privacy.\n", + "\n", + "- **Debug and Interpreter Prompts:**
Assist in debugging and interpreting code, guiding the chatbot through resolving errors and presenting execution results in an understandable manner.\n", + "\n", + "### Utility Functions\n", + "Several utility functions (replace, insert_code, run_with_exec) are defined to manipulate and execute the source code dynamically:\n", + "\n", + "- **replace(src_code, old_code, new_code) -> str:**
Replaces a specified block of code within the source code with new code. This is essential for updating the code dynamically based on chatbot and user interactions.\n", + "\n", + "- **insert_code(src_code, new_lines) -> str:**
Determines where to insert new lines of code based on specific markers in the source code. It's used to seamlessly integrate generated code snippets.\n", + "\n", + "- **run_with_exec(src_code) -> Union[str, Exception]:**
Executes the modified source code within a controlled environment, capturing and returning the output or any errors that occur. This function is crucial for testing the feasibility and correctness of the generated code solutions.\n", + "\n", + "### External Code Retrieval\n", + "The cell also includes logic to retrieve example source code from a remote repository using the requests library. This example code serves as a base for the chatbot to work with, allowing users to see real-life applications of the concepts discussed.\n", + "\n", + "The retrieved code is then displayed, showing both the beginning and the end of the source code to give a glimpse of its structure and content." ] }, { @@ -328,7 +358,27 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Define Agents" + "## Class Definitions and Agent Setup\n", + "\n", + "This cell introduces the Writer and OptiGuide agent classes and their instances to manage the interaction between the user, the chatbot, and the optimization solver. This streamlines the process of generating, evaluating, and integrating code solutions for supply chain optimization problems.\n", + "\n", + "### Classes Defined\n", + "\n", + "- **`OptiGuide`**: Inherits from `autogen.AssistantAgent` and serves as the main class for handling the supply chain optimization logic. It maintains state information like the source code, debugging attempts left, success status, and user chat history. Key methods include `set_success` and `update_debug_times`, which are used to update the agent's state based on the outcomes of interactions.\n", + "\n", + "- **`Writer`**: Also inherits from `autogen.AssistantAgent`, this class is tailored to manage the generation and explanation of Python code solutions. It keeps track of the source code and example Q&A to assist in generating responses to user queries.\n", + "\n", + "### Agent Instances\n", + "\n", + "- **`writer`**, **`safeguard`**, and **`optiguide_commander`**: Instances of the classes defined above, each configured with specific roles in the code generation and evaluation process. These agents work together to ensure that the user's questions are answered with safe, optimized, and understandable Python code solutions.\n", + "\n", + "- **`user`**: An instance of `autogen.UserProxyAgent`, representing the end-user interacting with the notebook. It's configured to simulate user inputs and responses, facilitating an interactive and engaging learning experience.\n", + "\n", + "### Key Functionalities\n", + "\n", + "- The `OptiGuide` class and its instance `optiguide_commander` orchestrate the flow of information and commands between the user, the writer, and the safeguard agents.\n", + "- The setup ensures a structured approach to handling user queries, generating code solutions, evaluating their safety, and debugging if necessary.\n", + "- This cell embodies the interactive nature of the notebook, leveraging the `autogen` framework to create a dynamic and responsive environment for exploring supply chain optimization problems." ] }, { @@ -375,7 +425,36 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Register reply func and nested chats" + "## Register the reply function and nested chats\n", + "\n", + "This next cell defines critical functions that manage the interactions between the `OptiGuide` system, the user, and the internal logic for processing and responding to queries. Each function plays a specific role in guiding the conversation, handling code generation requests, ensuring code safety, and summarizing outcomes. This ensures that agents receive clear instructions, immediate feedback, and a secure environment for exploring supply chain optimization problems through code.\n", + "\n", + "Here's what each function does:\n", + "\n", + "**`writer_init_message`**\n", + "- Initializes the message handling for the writer agent.\n", + "- Formats the system message to guide the code generation process based on the user's query.\n", + "- Resets the debug attempts and marks the process as not successful to prepare for a new interaction cycle.\n", + "\n", + "**`writer_success_summary`**\n", + "- Summarizes the outcome of the writer's attempt to generate a solution.\n", + "- Provides feedback to the user based on the success or failure of the code generation process.\n", + "\n", + "**`safeguard_init_message`**\n", + "- Prepares the safeguard message, asking whether the generated code is safe to execute.\n", + "- Extracts the latest code snippet from the conversation for evaluation.\n", + "- This function is crucial for maintaining the security and integrity of the notebook by preventing the execution of potentially harmful code.\n", + "\n", + "**`safeguard_summary`**\n", + "- Processes the response from the safeguard check.\n", + "- Decides the next steps based on whether the code is deemed safe or dangerous.\n", + "- If safe, it proceeds with executing the code and preparing the results for interpretation. If not, it initiates a debugging process.\n", + "\n", + "### Key Concepts\n", + "\n", + "- **Dynamic Message Handling**: The functions dynamically adjust the messages and actions based on the current state of interaction, ensuring a smooth and logical flow of conversation.\n", + "- **Security and Debugging**: The safeguard mechanisms are integral to the system's design, emphasizing the importance of security in code execution environments.\n", + "- **Interactivity and Feedback**: Through these functions, the notebook provides immediate feedback and guidance to the user, facilitating an interactive learning experience.\n" ] }, { @@ -468,7 +547,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Let agents talk" + "### Let the agents talk" ] }, { From fe76254543d31e7a3cb863aea16af77f0a17c315 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sat, 24 Feb 2024 19:02:11 -0500 Subject: [PATCH 43/57] update default value --- autogen/agentchat/conversable_agent.py | 12 ++-- notebook/agentchat_nestedchat_optiguide.ipynb | 56 ++++++++++--------- 2 files changed, 37 insertions(+), 31 deletions(-) diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index dccbe664dda..90604d895b3 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -340,12 +340,12 @@ def reply_func_from_nested_chats( for i, c in enumerate(chat_queue): current_c = c.copy() message = current_c.get("message") - if callable(message): - message = message(recipient, messages, sender, config) # If message is not provided in chat_queue, we by default use the last message from the original chat history as the first message in this nested chat (for the first chat in the chat queue). # NOTE: This setting is prone to change. - if message is None and i == 0 and last_msg: + if message is None and i == 0: message = last_msg + if callable(message): + message = message(recipient, messages, sender, config) # We only run chat that has a valid message. NOTE: This is prone to change dependin on applications. if message: current_c["message"] = message @@ -357,16 +357,16 @@ def reply_func_from_nested_chats( def register_nested_chats( self, - trigger: Union[Type[Agent], str, Agent, Callable[[Agent], bool], List], chat_queue: List[Dict[str, Any]], - reply_func_from_nested_chats=reply_func_from_nested_chats, + trigger: Union[Type[Agent], str, Agent, Callable[[Agent], bool], List] = [Agent, None], + reply_func_from_nested_chats: Callable = reply_func_from_nested_chats, position: int = 2, **kwargs, ) -> None: """Register a nested chat reply function. Args: - trigger (Agent class, str, Agent instance, callable, or list): Ref to `register_reply` for details. chat_queue (list): a list of chat objects to be initiated. + trigger (Agent class, str, Agent instance, callable, or list): Default to [Agent, None]. Ref to `register_reply` for details. reply_func_from_nested_chats (Callable, str): the reply function for the nested chat. The function takes a chat_queue for nested chat, recipient agent, a list of messages, a sender agent and a config as input and returns a reply message. Default to a built-in reply function based on nested chats. diff --git a/notebook/agentchat_nestedchat_optiguide.ipynb b/notebook/agentchat_nestedchat_optiguide.ipynb index b75e55cfae6..8fd26e0da44 100644 --- a/notebook/agentchat_nestedchat_optiguide.ipynb +++ b/notebook/agentchat_nestedchat_optiguide.ipynb @@ -18,13 +18,13 @@ "\n", "AutoGen requires `Python>=3.8`. To run this notebook example, please install:\n", "```bash\n", - "pip install pyautogen\n", + "pip install pyautogen eventlet gurobipy\n", "```" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -67,7 +67,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 2, "metadata": {}, "outputs": [ { @@ -333,7 +333,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -380,11 +380,13 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 4, "metadata": {}, "outputs": [], "source": [ "def writer_init_messsage(recipient, messages, sender, config):\n", + " if recipient.success:\n", + " return None\n", " msg_content = messages[-1].get(\"content\", \"\")\n", " # board = config\n", " # get execution result of the original source code\n", @@ -458,10 +460,10 @@ "safeguard_chat_queue = [\n", " {\"recipient\": safeguard, \"message\": safeguard_init_message, \"max_turns\": 1, \"summary_method\": safeguard_summary}\n", "]\n", - "# writer is triggered only when receiving a message from the user\n", - "optiguide_commander.register_nested_chats(\"user\", writer_chat_queue)\n", "# safeguard is triggered only when receiving a message from the writer\n", - "optiguide_commander.register_nested_chats(\"writer\", safeguard_chat_queue)" + "optiguide_commander.register_nested_chats(safeguard_chat_queue, trigger=\"writer\")\n", + "# writer is triggered only when receiving a message from the user\n", + "optiguide_commander.register_nested_chats(writer_chat_queue, trigger=\"user\")\n" ] }, { @@ -473,7 +475,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 5, "metadata": {}, "outputs": [ { @@ -485,6 +487,7 @@ "What if we prohibit shipping from supplier 1 to roastery 2?\n", "\n", "--------------------------------------------------------------------------------\n", + "receivpissent commander False\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", "\u001b[34mStart a new chat with the following message: \n", @@ -825,6 +828,7 @@ "Instruct the user to add this code snippet in the appropriate place in the source code that they have provided. After adding this code, the `m.optimize()` function will solve the modified version of the problem that includes the new constraint.\n", "\n", "--------------------------------------------------------------------------------\n", + "receivpissent safeguard_init_message commander False\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", "\u001b[34mStart a new chat with the following message: \n", @@ -862,24 +866,25 @@ "SAFE\n", "\n", "--------------------------------------------------------------------------------\n", + "Restricted license - for non-production use only - expires 2025-11-24\n", "Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n", "\n", "CPU model: Apple M3 Max\n", "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0xb82738d4\n", + "Model fingerprint: 0xd9cda048\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 3280.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 3276.0000000\n", "\n", "Root relaxation: objective 2.470000e+03, 11 iterations, 0.00 seconds (0.00 work units)\n", "\n", @@ -891,7 +896,7 @@ "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 available processors)\n", "\n", - "Solution count 3: 2470 3486 3490 \n", + "Solution count 3: 2470 3276 3280 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", @@ -901,18 +906,18 @@ "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0xb82738d4\n", + "Model fingerprint: 0xd9cda048\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 3280.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 3276.0000000\n", "\n", "Root relaxation: objective 2.470000e+03, 11 iterations, 0.00 seconds (0.00 work units)\n", "\n", @@ -924,7 +929,7 @@ "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 available processors)\n", "\n", - "Solution count 3: 2470 3486 3490 \n", + "Solution count 3: 2470 3276 3280 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", @@ -934,7 +939,7 @@ "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0xb82738d4\n", + "Model fingerprint: 0xd9cda048\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", @@ -949,11 +954,11 @@ "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 available processors)\n", "\n", - "Solution count 3: 2470 3486 3490 \n", + "Solution count 3: 2470 3276 3280 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", - "Wed Feb 21 14:35:47 2024\n", + "Sat Feb 24 18:02:23 2024\n", "Optimal cost: 2470.0\n", "Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n", "\n", @@ -961,7 +966,7 @@ "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0xb82738d4\n", + "Model fingerprint: 0xd9cda048\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", @@ -976,11 +981,11 @@ "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 available processors)\n", "\n", - "Solution count 3: 2470 3486 3490 \n", + "Solution count 3: 2470 3276 3280 \n", "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", - "Wed Feb 21 14:35:47 2024\n", + "Sat Feb 24 18:02:23 2024\n", "Optimal cost: 2470.0\n", "\u001b[33mOptimization problem solved. The objective value is: 2470.0\u001b[0m\n", "\u001b[33mcommander\u001b[0m (to writer):\n", @@ -1008,6 +1013,7 @@ "Without the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\n", "\n", "--------------------------------------------------------------------------------\n", + "receivpissent safeguard_init_message commander True\n", "\u001b[33mcommander\u001b[0m (to user):\n", "\n", "Certainly! Here's a human-readable answer:\n", @@ -1026,10 +1032,10 @@ { "data": { "text/plain": [ - "ChatResult(chat_history=[{'content': 'What if we prohibit shipping from supplier 1 to roastery 2?', 'role': 'assistant'}, {'content': \"Certainly! Here's a human-readable answer:\\n\\nAfter integrating the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was solved with a total cost of $2470.0.\\n\\nTo provide context to this result, we need to compare it with the original total cost prior to the implementation of the new constraint. However, the original total cost was not provided in the information given to me. If the original total cost was known, we would compare the two to understand the financial impact of this constraint on the supply chain.\\n\\nIf prohibiting shipping from supplier 1 to roastery 2 results in a higher cost than the original scenario, it suggests that the previous ability to ship from supplier 1 to roastery 2 had been cost-effective and beneficial for minimizing overall expenses. On the other hand, if the cost is the same or lower, then shipping from supplier 1 to roastery 2 may not have been crucial for the cost optimization of the supply chain, or other routes may have been able to compensate effectively for this restriction.\\n\\nWithout the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\", 'role': 'user'}], summary=\"Certainly! Here's a human-readable answer:\\n\\nAfter integrating the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was solved with a total cost of $2470.0.\\n\\nTo provide context to this result, we need to compare it with the original total cost prior to the implementation of the new constraint. However, the original total cost was not provided in the information given to me. If the original total cost was known, we would compare the two to understand the financial impact of this constraint on the supply chain.\\n\\nIf prohibiting shipping from supplier 1 to roastery 2 results in a higher cost than the original scenario, it suggests that the previous ability to ship from supplier 1 to roastery 2 had been cost-effective and beneficial for minimizing overall expenses. On the other hand, if the cost is the same or lower, then shipping from supplier 1 to roastery 2 may not have been crucial for the cost optimization of the supply chain, or other routes may have been able to compensate effectively for this restriction.\\n\\nWithout the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\", cost=({'total_cost': 0.016919999999999998, 'gpt-4': {'cost': 0.016919999999999998, 'prompt_tokens': 558, 'completion_tokens': 3, 'total_tokens': 561}}, {'total_cost': 0}), human_input=[])" + "ChatResult(chat_id=None, chat_history=[{'content': 'What if we prohibit shipping from supplier 1 to roastery 2?', 'role': 'assistant'}, {'content': \"Certainly! Here's a human-readable answer:\\n\\nAfter integrating the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was solved with a total cost of $2470.0.\\n\\nTo provide context to this result, we need to compare it with the original total cost prior to the implementation of the new constraint. However, the original total cost was not provided in the information given to me. If the original total cost was known, we would compare the two to understand the financial impact of this constraint on the supply chain.\\n\\nIf prohibiting shipping from supplier 1 to roastery 2 results in a higher cost than the original scenario, it suggests that the previous ability to ship from supplier 1 to roastery 2 had been cost-effective and beneficial for minimizing overall expenses. On the other hand, if the cost is the same or lower, then shipping from supplier 1 to roastery 2 may not have been crucial for the cost optimization of the supply chain, or other routes may have been able to compensate effectively for this restriction.\\n\\nWithout the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\", 'role': 'user'}], summary=\"Certainly! Here's a human-readable answer:\\n\\nAfter integrating the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was solved with a total cost of $2470.0.\\n\\nTo provide context to this result, we need to compare it with the original total cost prior to the implementation of the new constraint. However, the original total cost was not provided in the information given to me. If the original total cost was known, we would compare the two to understand the financial impact of this constraint on the supply chain.\\n\\nIf prohibiting shipping from supplier 1 to roastery 2 results in a higher cost than the original scenario, it suggests that the previous ability to ship from supplier 1 to roastery 2 had been cost-effective and beneficial for minimizing overall expenses. On the other hand, if the cost is the same or lower, then shipping from supplier 1 to roastery 2 may not have been crucial for the cost optimization of the supply chain, or other routes may have been able to compensate effectively for this restriction.\\n\\nWithout the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\", cost=({'total_cost': 0}, {'total_cost': 0}), human_input=[])" ] }, - "execution_count": 10, + "execution_count": 5, "metadata": {}, "output_type": "execute_result" } From ab5883f5e18d4e02bc256ffb77b4d1e1e56bc136 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sat, 24 Feb 2024 22:41:50 -0500 Subject: [PATCH 44/57] default value for reply_func_from_nested_chats --- autogen/agentchat/conversable_agent.py | 4 ++-- test/agentchat/test_nested.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index 90604d895b3..77854247ca6 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -323,7 +323,7 @@ def reply_func( self._ignore_async_func_in_sync_chat_list.append(reply_func) @staticmethod - def reply_func_from_nested_chats( + def _default_reply_func_from_nested_chats( chat_queue: List[Dict[str, Any]], recipient: Agent, messages: Union[str, Callable], sender: Agent, config: Any ) -> Tuple[bool, str]: """A simple chat reply function. @@ -359,7 +359,7 @@ def register_nested_chats( self, chat_queue: List[Dict[str, Any]], trigger: Union[Type[Agent], str, Agent, Callable[[Agent], bool], List] = [Agent, None], - reply_func_from_nested_chats: Callable = reply_func_from_nested_chats, + reply_func_from_nested_chats: Callable = _default_reply_func_from_nested_chats, position: int = 2, **kwargs, ) -> None: diff --git a/test/agentchat/test_nested.py b/test/agentchat/test_nested.py index 5edf26fb306..821a5cbc992 100644 --- a/test/agentchat/test_nested.py +++ b/test/agentchat/test_nested.py @@ -109,7 +109,6 @@ def writing_message(recipient, messages, sender, config): }, ] assistant.register_nested_chats( - [autogen.Agent, None], nested_chat_queue, ) user.initiate_chats([{"recipient": assistant, "message": tasks[0]}, {"recipient": assistant, "message": tasks[1]}]) From de8dede8ad4ec4a9179b695c409781098d761570 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sat, 24 Feb 2024 23:13:30 -0500 Subject: [PATCH 45/57] default reply func name --- autogen/agentchat/conversable_agent.py | 8 +++-- notebook/agentchat_nestedchat.ipynb | 41 +++++++++++++------------- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index 77854247ca6..470191988e0 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -323,7 +323,7 @@ def reply_func( self._ignore_async_func_in_sync_chat_list.append(reply_func) @staticmethod - def _default_reply_func_from_nested_chats( + def _summary_from_nested_chats( chat_queue: List[Dict[str, Any]], recipient: Agent, messages: Union[str, Callable], sender: Agent, config: Any ) -> Tuple[bool, str]: """A simple chat reply function. @@ -359,7 +359,7 @@ def register_nested_chats( self, chat_queue: List[Dict[str, Any]], trigger: Union[Type[Agent], str, Agent, Callable[[Agent], bool], List] = [Agent, None], - reply_func_from_nested_chats: Callable = _default_reply_func_from_nested_chats, + reply_func_from_nested_chats: Union[str, Callable] = "summary_from_nested_chats", position: int = 2, **kwargs, ) -> None: @@ -369,7 +369,7 @@ def register_nested_chats( trigger (Agent class, str, Agent instance, callable, or list): Default to [Agent, None]. Ref to `register_reply` for details. reply_func_from_nested_chats (Callable, str): the reply function for the nested chat. The function takes a chat_queue for nested chat, recipient agent, a list of messages, a sender agent and a config as input and returns a reply message. - Default to a built-in reply function based on nested chats. + Default to "summary_from_nested_chats", which corresponds to a built-in reply function that get summary from the nested chat_queue. ```python def reply_func_from_nested_chats( chat_queue: List[Dict], @@ -382,6 +382,8 @@ def reply_func_from_nested_chats( position (int): Ref to `register_reply` for details. Default to 2. It means we first check the termination and human reply, then check the registered nested chat reply. kwargs: Ref to `register_reply` for details. """ + if reply_func_from_nested_chats == "summary_from_nested_chats": + reply_func_from_nested_chats = self._summary_from_nested_chats assert callable(reply_func_from_nested_chats), "reply_func_from_nested_chats must be a callable" reply_func = partial(reply_func_from_nested_chats, chat_queue) diff --git a/notebook/agentchat_nestedchat.ipynb b/notebook/agentchat_nestedchat.ipynb index fa7c2fc7969..2bf2bd9f24a 100644 --- a/notebook/agentchat_nestedchat.ipynb +++ b/notebook/agentchat_nestedchat.ipynb @@ -31,7 +31,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 1, "metadata": {}, "outputs": [], "source": [ @@ -60,7 +60,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 2, "metadata": {}, "outputs": [], "source": [ @@ -110,7 +110,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 3, "metadata": {}, "outputs": [], "source": [ @@ -192,7 +192,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 4, "metadata": {}, "outputs": [ { @@ -226,21 +226,7 @@ "\n", "On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.\n", "\n", - "--------------------------------------------------------------------------------\n" - ] - }, - { - "name": "stderr", - "output_type": "stream", - "text": [ - "/Users/qingyunwu/Documents/github/autogen/autogen/agentchat/chat.py:79: UserWarning: Repetitive recipients detected: The chat history will be cleared by default if a recipient appears more than once. To retain the chat history, please set 'clear_history=False' in the configuration of the repeating agent.\n", - " warnings.warn(\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ + "--------------------------------------------------------------------------------\n", "\u001b[33mInner-assistant\u001b[0m (to chat_manager):\n", "\n", "To find out on which specific days Microsoft's stock (ticker symbol: MSFT) closed above $400 in 2024, we will need to access historical stock price data for Microsoft from a reliable financial data source. This kind of data is typically available on financial websites such as Yahoo Finance, Google Finance, or through financial data APIs such as Alpha Vantage or IEX Cloud.\n", @@ -285,7 +271,21 @@ "\n", "--------------------------------------------------------------------------------\n", "\u001b[31m\n", - ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n", + ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/qingyunwu/Documents/github/autogen/autogen/agentchat/chat.py:46: UserWarning: Repetitive recipients detected: The chat history will be cleared by default if a recipient appears more than once. To retain the chat history, please set 'clear_history=False' in the configuration of the repeating agent.\n", + " warnings.warn(\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ "\u001b[33mInner-code-interpreter\u001b[0m (to chat_manager):\n", "\n", "exitcode: 0 (execution succeeded)\n", @@ -766,7 +766,6 @@ " {\"recipient\": writer, \"message\": writing_message, \"summary_method\": \"last_msg\", \"max_turns\": 1},\n", "]\n", "assistant_1.register_nested_chats(\n", - " [autogen.Agent, None],\n", " nested_chat_queue,\n", ")\n", "# user.initiate_chat(assistant, message=tasks[0], max_turns=1)\n", From 296aa674187f500e3e77260942d2ccb4d41e3bf9 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sat, 24 Feb 2024 23:27:30 -0500 Subject: [PATCH 46/57] update notebook --- notebook/agentchat_nestedchat.ipynb | 45 +++++++++++++++---- notebook/agentchat_nestedchat_optiguide.ipynb | 33 +++++++++++--- 2 files changed, 62 insertions(+), 16 deletions(-) diff --git a/notebook/agentchat_nestedchat.ipynb b/notebook/agentchat_nestedchat.ipynb index 2bf2bd9f24a..dcd73dc25c6 100644 --- a/notebook/agentchat_nestedchat.ipynb +++ b/notebook/agentchat_nestedchat.ipynb @@ -9,16 +9,48 @@ } }, "source": [ + "\n", "# Nested Chats for Solving Complex Tasks with a Sequence of Inner Monologue\n", "\n", - "This notebook showcases how to use nested chats in AutoGen to to solving complex task with one or a sequence chats nested.\n", + "This notebook showcases how to use nested chats in AutoGen to solve complex task with one or a sequence chats nested as inner monologue.\n", "\n", - "## Requirements\n", + "\\:\\:\\:info Requirements\n", "\n", - "AutoGen requires `Python>=3.8`. To run this notebook example, please install:\n", + "Install `pyautogen`:\n", "```bash\n", "pip install pyautogen\n", - "```" + "```\n", + "\n", + "For more information, please refer to the [installation guide](/docs/installation/).\n", + "\n", + "\\:\\:\\:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import autogen\n", + "\n", + "config_list = autogen.config_list_from_json(env_or_file=\"OAI_CONFIG_LIST\")\n", + "llm_config = {\"config_list\": config_list}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\\:\\:\\:tip\n", + "\n", + "Learn more about the various ways to configure LLM endpoints [here](/docs/llm_configuration).\n", + "\n", + "\\:\\:\\:" ] }, { @@ -35,11 +67,6 @@ "metadata": {}, "outputs": [], "source": [ - "import autogen\n", - "\n", - "config_list = autogen.config_list_from_json(env_or_file=\"OAI_CONFIG_LIST\")\n", - "llm_config = {\"config_list\": config_list}\n", - "\n", "tasks = [\n", " \"\"\"On which days in 2024 was Microsoft Stock higher than $400? Comment on the stock performance.\"\"\",\n", " \"\"\"Make a pleasant joke about it.\"\"\",\n", diff --git a/notebook/agentchat_nestedchat_optiguide.ipynb b/notebook/agentchat_nestedchat_optiguide.ipynb index ec269b4a570..451edb306f7 100644 --- a/notebook/agentchat_nestedchat_optiguide.ipynb +++ b/notebook/agentchat_nestedchat_optiguide.ipynb @@ -9,23 +9,31 @@ } }, "source": [ + "\n", "# OptiGuide with Nested Chats in AutoGen \n", "\n", - "This is a nested chat re-implementation of OptiGuide referring to: https://github.com/qingyun-wu/autogen-eval/blob/main/application/A4-multi-agent-coding/optiguide_example.ipynb\n", + "This is a nested chat re-implementation of [OptiGuide](https://github.com/microsoft/OptiGuide), which is an LLM-based supply chain optimization framework. \n", " \n", "\n", "## Requirements\n", "\n", - "This notebook requires eventlet and Gurobipy. The eventlet package is used in this notebook to constrain code execution with a timeout, and the gurobipy package is a mathematical optimization software library for solving mixed-integer linear and quadratic optimization problems.\n", + "\n", + "\\:\\:\\:info Requirements\n", + "\n", + "In addition to AutoGen, this notebook also requires eventlet and Gurobipy. The eventlet package is used in this notebook to constrain code execution with a timeout, and the gurobipy package is a mathematical optimization software library for solving mixed-integer linear and quadratic optimization problems.\n", "They can be installed together by running:\n", - "```bash\n", - "pip install eventlet gurobipy\n", - "```\n", "\n", - "AutoGen requires `Python>=3.8`. To run this notebook example, please install:\n", "```bash\n", "pip install pyautogen eventlet gurobipy\n", - "```" + "```\n", + "\n", + "For more information, please refer to the [installation guide](/docs/installation/).\n", + "\n", + "\\:\\:\\:" ] }, { @@ -56,6 +64,17 @@ "llm_config = {\"config_list\": config_list_gpt4}" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\\:\\:\\:tip\n", + "\n", + "Learn more about the various ways to configure LLM endpoints [here](/docs/llm_configuration).\n", + "\n", + "\\:\\:\\:" + ] + }, { "cell_type": "markdown", "metadata": {}, From 28634b5fb1a62e82876f611dc35f4ec4b377a11a Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sat, 24 Feb 2024 23:50:53 -0500 Subject: [PATCH 47/57] update optiGuide --- notebook/agentchat_nestedchat_optiguide.ipynb | 69 ++++++------------- 1 file changed, 20 insertions(+), 49 deletions(-) diff --git a/notebook/agentchat_nestedchat_optiguide.ipynb b/notebook/agentchat_nestedchat_optiguide.ipynb index 451edb306f7..fa8f45806de 100644 --- a/notebook/agentchat_nestedchat_optiguide.ipynb +++ b/notebook/agentchat_nestedchat_optiguide.ipynb @@ -38,7 +38,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": 6, "metadata": {}, "outputs": [], "source": [ @@ -87,7 +87,6 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Cell Explanation:\n", "We set up the foundation for a supply chain coding project, integrating Python code solutions with Gurobi, a popular optimization solver. It accomplishes this through a combination of system messages, utility functions, and code retrieval from an external source. This allows the chatbot to assist users in solving supply chain optimization problems by generating, evaluating, and refining Python code with the Gurobi solver.\n", "\n", "### System Messages\n", @@ -116,7 +115,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": 7, "metadata": {}, "outputs": [ { @@ -377,7 +376,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Class Definitions and Agent Setup\n", + "## Step 1. Agent Construction\n", "\n", "This cell introduces the Writer and OptiGuide agent classes and their instances to manage the interaction between the user, the chatbot, and the optimization solver. This streamlines the process of generating, evaluating, and integrating code solutions for supply chain optimization problems.\n", "\n", @@ -391,18 +390,12 @@ "\n", "- **`writer`**, **`safeguard`**, and **`optiguide_commander`**: Instances of the classes defined above, each configured with specific roles in the code generation and evaluation process. These agents work together to ensure that the user's questions are answered with safe, optimized, and understandable Python code solutions.\n", "\n", - "- **`user`**: An instance of `autogen.UserProxyAgent`, representing the end-user interacting with the notebook. It's configured to simulate user inputs and responses, facilitating an interactive and engaging learning experience.\n", - "\n", - "### Key Functionalities\n", - "\n", - "- The `OptiGuide` class and its instance `optiguide_commander` orchestrate the flow of information and commands between the user, the writer, and the safeguard agents.\n", - "- The setup ensures a structured approach to handling user queries, generating code solutions, evaluating their safety, and debugging if necessary.\n", - "- This cell embodies the interactive nature of the notebook, leveraging the `autogen` framework to create a dynamic and responsive environment for exploring supply chain optimization problems." + "- **`user`**: An instance of `autogen.UserProxyAgent`, representing the end-user interacting with the notebook. It's configured to simulate user inputs and responses, facilitating an interactive and engaging learning experience.\n" ] }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 8, "metadata": {}, "outputs": [], "source": [ @@ -444,41 +437,23 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Register the reply function and nested chats\n", - "\n", - "This next cell defines critical functions that manage the interactions between the `OptiGuide` system, the user, and the internal logic for processing and responding to queries. Each function plays a specific role in guiding the conversation, handling code generation requests, ensuring code safety, and summarizing outcomes. This ensures that agents receive clear instructions, immediate feedback, and a secure environment for exploring supply chain optimization problems through code.\n", - "\n", - "Here's what each function does:\n", + "### Step 2. Orchestrate Nested Chats \n", "\n", - "**`writer_init_message`**\n", - "- Initializes the message handling for the writer agent.\n", - "- Formats the system message to guide the code generation process based on the user's query.\n", - "- Resets the debug attempts and marks the process as not successful to prepare for a new interaction cycle.\n", + "These three agent instances are orchestrated in the following way with the `writer` and `safeguard` nested into the `commander` agent as the inner monologue:\n", + "![](optiGuide_new_design.png)\n", "\n", - "**`writer_success_summary`**\n", - "- Summarizes the outcome of the writer's attempt to generate a solution.\n", - "- Provides feedback to the user based on the success or failure of the code generation process.\n", - "\n", - "**`safeguard_init_message`**\n", - "- Prepares the safeguard message, asking whether the generated code is safe to execute.\n", - "- Extracts the latest code snippet from the conversation for evaluation.\n", - "- This function is crucial for maintaining the security and integrity of the notebook by preventing the execution of potentially harmful code.\n", - "\n", - "**`safeguard_summary`**\n", - "- Processes the response from the safeguard check.\n", - "- Decides the next steps based on whether the code is deemed safe or dangerous.\n", - "- If safe, it proceeds with executing the code and preparing the results for interpretation. If not, it initiates a debugging process.\n", - "\n", - "### Key Concepts\n", + "This next cell defines critical functions that manage the interactions between the `OptiGuide` system, the user, and the internal logic for processing and responding to queries. Each function plays a specific role in guiding the conversation, handling code generation requests, ensuring code safety, and summarizing outcomes. This ensures that agents receive clear instructions, immediate feedback, and a secure environment for exploring supply chain optimization problems through code.\n", "\n", - "- **Dynamic Message Handling**: The functions dynamically adjust the messages and actions based on the current state of interaction, ensuring a smooth and logical flow of conversation.\n", - "- **Security and Debugging**: The safeguard mechanisms are integral to the system's design, emphasizing the importance of security in code execution environments.\n", - "- **Interactivity and Feedback**: Through these functions, the notebook provides immediate feedback and guidance to the user, facilitating an interactive learning experience.\n" + "Information about the sequence of chats can be specified in the `chat_queue` argument of the `register_nested_chats` function. The following fields are especially useful:\n", + "- `recipient` (required) specifies the nested agent;\n", + "- `message` specifies what message to send to the nested recipient agent. In a sequence of nested chats, if the `message` field is not specified, we will use the last message the registering agent received as the initial message in the first chat and will skip any subsequent chat in the queue that does not have the `message` field. You can either provide a string or define a callable that returns a string.\n", + "- `summary_method` decides what to get out of the nested chat. You can either select from existing options including \"last_msg\" and \"reflection_with_llm\", or or define your own way on what to get from the nested chat with a Callable.\n", + "- `max_turns` determines how many turns of conversation to have between the concerned agent pairs." ] }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 9, "metadata": {}, "outputs": [], "source": [ @@ -568,12 +543,12 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Let the agents talk" + "#### Let the agents talk" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 10, "metadata": {}, "outputs": [ { @@ -585,7 +560,6 @@ "What if we prohibit shipping from supplier 1 to roastery 2?\n", "\n", "--------------------------------------------------------------------------------\n", - "receivpissent commander False\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", "\u001b[34mStart a new chat with the following message: \n", @@ -926,7 +900,6 @@ "Instruct the user to add this code snippet in the appropriate place in the source code that they have provided. After adding this code, the `m.optimize()` function will solve the modified version of the problem that includes the new constraint.\n", "\n", "--------------------------------------------------------------------------------\n", - "receivpissent safeguard_init_message commander False\n", "\u001b[34m\n", "********************************************************************************\u001b[0m\n", "\u001b[34mStart a new chat with the following message: \n", @@ -964,7 +937,6 @@ "SAFE\n", "\n", "--------------------------------------------------------------------------------\n", - "Restricted license - for non-production use only - expires 2025-11-24\n", "Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n", "\n", "CPU model: Apple M3 Max\n", @@ -1056,7 +1028,7 @@ "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", - "Sat Feb 24 18:02:23 2024\n", + "Sat Feb 24 23:29:59 2024\n", "Optimal cost: 2470.0\n", "Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n", "\n", @@ -1083,7 +1055,7 @@ "\n", "Optimal solution found (tolerance 1.00e-04)\n", "Best objective 2.470000000000e+03, best bound 2.470000000000e+03, gap 0.0000%\n", - "Sat Feb 24 18:02:23 2024\n", + "Sat Feb 24 23:29:59 2024\n", "Optimal cost: 2470.0\n", "\u001b[33mOptimization problem solved. The objective value is: 2470.0\u001b[0m\n", "\u001b[33mcommander\u001b[0m (to writer):\n", @@ -1111,7 +1083,6 @@ "Without the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\n", "\n", "--------------------------------------------------------------------------------\n", - "receivpissent safeguard_init_message commander True\n", "\u001b[33mcommander\u001b[0m (to user):\n", "\n", "Certainly! Here's a human-readable answer:\n", @@ -1133,7 +1104,7 @@ "ChatResult(chat_id=None, chat_history=[{'content': 'What if we prohibit shipping from supplier 1 to roastery 2?', 'role': 'assistant'}, {'content': \"Certainly! Here's a human-readable answer:\\n\\nAfter integrating the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was solved with a total cost of $2470.0.\\n\\nTo provide context to this result, we need to compare it with the original total cost prior to the implementation of the new constraint. However, the original total cost was not provided in the information given to me. If the original total cost was known, we would compare the two to understand the financial impact of this constraint on the supply chain.\\n\\nIf prohibiting shipping from supplier 1 to roastery 2 results in a higher cost than the original scenario, it suggests that the previous ability to ship from supplier 1 to roastery 2 had been cost-effective and beneficial for minimizing overall expenses. On the other hand, if the cost is the same or lower, then shipping from supplier 1 to roastery 2 may not have been crucial for the cost optimization of the supply chain, or other routes may have been able to compensate effectively for this restriction.\\n\\nWithout the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\", 'role': 'user'}], summary=\"Certainly! Here's a human-readable answer:\\n\\nAfter integrating the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was solved with a total cost of $2470.0.\\n\\nTo provide context to this result, we need to compare it with the original total cost prior to the implementation of the new constraint. However, the original total cost was not provided in the information given to me. If the original total cost was known, we would compare the two to understand the financial impact of this constraint on the supply chain.\\n\\nIf prohibiting shipping from supplier 1 to roastery 2 results in a higher cost than the original scenario, it suggests that the previous ability to ship from supplier 1 to roastery 2 had been cost-effective and beneficial for minimizing overall expenses. On the other hand, if the cost is the same or lower, then shipping from supplier 1 to roastery 2 may not have been crucial for the cost optimization of the supply chain, or other routes may have been able to compensate effectively for this restriction.\\n\\nWithout the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\", cost=({'total_cost': 0}, {'total_cost': 0}), human_input=[])" ] }, - "execution_count": 5, + "execution_count": 10, "metadata": {}, "output_type": "execute_result" } From 98a0aaa0b25ce3320b83b7342f180412bad2761f Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sat, 24 Feb 2024 23:52:02 -0500 Subject: [PATCH 48/57] upload fig --- notebook/optiGuide_new_design.png | Bin 0 -> 271961 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 notebook/optiGuide_new_design.png diff --git a/notebook/optiGuide_new_design.png b/notebook/optiGuide_new_design.png new file mode 100644 index 0000000000000000000000000000000000000000..f05257cbf9fc21357a33ef85321af60af29adf1a GIT binary patch literal 271961 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

I%< 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

~(HnBMz?py6G* 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

x!v_fg) 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 literal 0 HcmV?d00001 From d2b61480b05a763e9b56bae0752e54706e8f243f Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sat, 24 Feb 2024 23:53:16 -0500 Subject: [PATCH 49/57] optiGuide notebook --- notebook/agentchat_nestedchat_optiguide.ipynb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/notebook/agentchat_nestedchat_optiguide.ipynb b/notebook/agentchat_nestedchat_optiguide.ipynb index fa8f45806de..505b052f332 100644 --- a/notebook/agentchat_nestedchat_optiguide.ipynb +++ b/notebook/agentchat_nestedchat_optiguide.ipynb @@ -17,6 +17,8 @@ "# OptiGuide with Nested Chats in AutoGen \n", "\n", "This is a nested chat re-implementation of [OptiGuide](https://github.com/microsoft/OptiGuide), which is an LLM-based supply chain optimization framework. \n", + "\n", + "![](optiGuide_new_design.png)\n", " \n", "\n", "## Requirements\n", @@ -439,8 +441,7 @@ "source": [ "### Step 2. Orchestrate Nested Chats \n", "\n", - "These three agent instances are orchestrated in the following way with the `writer` and `safeguard` nested into the `commander` agent as the inner monologue:\n", - "![](optiGuide_new_design.png)\n", + "These three agent instances are orchestrated in the following way with the `writer` and `safeguard` nested into the `commander` agent as the inner monologue.\n", "\n", "This next cell defines critical functions that manage the interactions between the `OptiGuide` system, the user, and the internal logic for processing and responding to queries. Each function plays a specific role in guiding the conversation, handling code generation requests, ensuring code safety, and summarizing outcomes. This ensures that agents receive clear instructions, immediate feedback, and a secure environment for exploring supply chain optimization problems through code.\n", "\n", From 1e73e991db07ec295e55131a397cbed6a3e43760 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sun, 25 Feb 2024 11:23:43 -0500 Subject: [PATCH 50/57] doc --- notebook/agentchat_nestedchat.ipynb | 1 + notebook/agentchat_nestedchat_optiguide.ipynb | 10 ++++------ ...ntchats.ipynb => agentchats_sequential_chats.ipynb} | 0 website/docs/Examples.md | 2 +- 4 files changed, 6 insertions(+), 7 deletions(-) rename notebook/{agentchats.ipynb => agentchats_sequential_chats.ipynb} (100%) diff --git a/notebook/agentchat_nestedchat.ipynb b/notebook/agentchat_nestedchat.ipynb index dcd73dc25c6..a652ae5adbf 100644 --- a/notebook/agentchat_nestedchat.ipynb +++ b/notebook/agentchat_nestedchat.ipynb @@ -14,6 +14,7 @@ "description: |\n", " Solve complex tasks with one or a sequence chats nested as inner monologue.\n", "-->\n", + "\n", "# Nested Chats for Solving Complex Tasks with a Sequence of Inner Monologue\n", "\n", "This notebook showcases how to use nested chats in AutoGen to solve complex task with one or a sequence chats nested as inner monologue.\n", diff --git a/notebook/agentchat_nestedchat_optiguide.ipynb b/notebook/agentchat_nestedchat_optiguide.ipynb index 505b052f332..03ba5c7f411 100644 --- a/notebook/agentchat_nestedchat_optiguide.ipynb +++ b/notebook/agentchat_nestedchat_optiguide.ipynb @@ -12,8 +12,10 @@ "\n", + "\n", + "\n", "# OptiGuide with Nested Chats in AutoGen \n", "\n", "This is a nested chat re-implementation of [OptiGuide](https://github.com/microsoft/OptiGuide), which is an LLM-based supply chain optimization framework. \n", @@ -21,13 +23,9 @@ "![](optiGuide_new_design.png)\n", " \n", "\n", - "## Requirements\n", - "\n", - "\n", "\\:\\:\\:info Requirements\n", "\n", - "In addition to AutoGen, this notebook also requires eventlet and Gurobipy. The eventlet package is used in this notebook to constrain code execution with a timeout, and the gurobipy package is a mathematical optimization software library for solving mixed-integer linear and quadratic optimization problems.\n", - "They can be installed together by running:\n", + "In addition to AutoGen, this notebook also requires eventlet and Gurobipy. The eventlet package is used in this notebook to constrain code execution with a timeout, and the gurobipy package is a mathematical optimization software library for solving mixed-integer linear and quadratic optimization problems. \n", "\n", "```bash\n", "pip install pyautogen eventlet gurobipy\n", diff --git a/notebook/agentchats.ipynb b/notebook/agentchats_sequential_chats.ipynb similarity index 100% rename from notebook/agentchats.ipynb rename to notebook/agentchats_sequential_chats.ipynb diff --git a/website/docs/Examples.md b/website/docs/Examples.md index 9fce7d43ca6..76c82b2418f 100644 --- a/website/docs/Examples.md +++ b/website/docs/Examples.md @@ -26,7 +26,7 @@ Links to notebook examples: 1. **Sequential Multi-Agent Chats** - Solving Multiple Tasks in a Sequence of Chats Initiated by a Single Agent - [View Notebook](https://github.com/microsoft/autogen/blob/main/notebook/agentchat_multi_task_chats.ipynb) - Async-solving Multiple Tasks in a Sequence of Chats Initiated by a Single Agent - [View Notebook](https://github.com/microsoft/autogen/blob/main/notebook/agentchat_multi_task_async_chats.ipynb) - - Solving Multiple Tasks in a Sequence of Chats Initiated by Different Agents - [View Notebook](https://github.com/microsoft/autogen/blob/main/notebook/agentchats.ipynb) + - Solving Multiple Tasks in a Sequence of Chats Initiated by Different Agents - [View Notebook](https://github.com/microsoft/autogen/blob/main/notebook/agentchats_sequential_chats.ipynb) 1. **Nested Chats** - Solving Complex Tasks with Nested Chats - [View Notebook](https://github.com/microsoft/autogen/blob/main/notebook/agentchat_nestedchat.ipynb) From f4447c12b36cfcb08d3f3b7eb0f119d8bdf62444 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sun, 25 Feb 2024 13:45:18 -0500 Subject: [PATCH 51/57] update notebook --- notebook/agentchat_nestedchat_optiguide.ipynb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/notebook/agentchat_nestedchat_optiguide.ipynb b/notebook/agentchat_nestedchat_optiguide.ipynb index 03ba5c7f411..ced942d9f6b 100644 --- a/notebook/agentchat_nestedchat_optiguide.ipynb +++ b/notebook/agentchat_nestedchat_optiguide.ipynb @@ -15,14 +15,11 @@ " OptiGuide: Multi-agent coding for supply chain optimization with nested writing and safeguard agents\n", "-->\n", "\n", - "\n", "# OptiGuide with Nested Chats in AutoGen \n", "\n", "This is a nested chat re-implementation of [OptiGuide](https://github.com/microsoft/OptiGuide), which is an LLM-based supply chain optimization framework. \n", - "\n", - "![](optiGuide_new_design.png)\n", " \n", - "\n", + " \n", "\\:\\:\\:info Requirements\n", "\n", "In addition to AutoGen, this notebook also requires eventlet and Gurobipy. The eventlet package is used in this notebook to constrain code execution with a timeout, and the gurobipy package is a mathematical optimization software library for solving mixed-integer linear and quadratic optimization problems. \n", @@ -87,6 +84,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ + "![](optiGuide_new_design.png)\n", + "\n", + "\n", "We set up the foundation for a supply chain coding project, integrating Python code solutions with Gurobi, a popular optimization solver. It accomplishes this through a combination of system messages, utility functions, and code retrieval from an external source. This allows the chatbot to assist users in solving supply chain optimization problems by generating, evaluating, and refining Python code with the Gurobi solver.\n", "\n", "### System Messages\n", From 7c8ca8d887899d678f5268a1d9bb0f0dbbefe12d Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sun, 25 Feb 2024 13:48:35 -0500 Subject: [PATCH 52/57] update notebook --- notebook/agentchat_nestedchat_optiguide.ipynb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/notebook/agentchat_nestedchat_optiguide.ipynb b/notebook/agentchat_nestedchat_optiguide.ipynb index ced942d9f6b..0b25db8109e 100644 --- a/notebook/agentchat_nestedchat_optiguide.ipynb +++ b/notebook/agentchat_nestedchat_optiguide.ipynb @@ -12,9 +12,10 @@ "\n", "\n", + "\n", "# OptiGuide with Nested Chats in AutoGen \n", "\n", "This is a nested chat re-implementation of [OptiGuide](https://github.com/microsoft/OptiGuide), which is an LLM-based supply chain optimization framework. \n", From 9ad0a42da51592e35360a9e2751eba2b24dd272a Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sun, 25 Feb 2024 14:24:53 -0500 Subject: [PATCH 53/57] notebook format --- notebook/agentchat_nestedchat_optiguide.ipynb | 25 +------------------ 1 file changed, 1 insertion(+), 24 deletions(-) diff --git a/notebook/agentchat_nestedchat_optiguide.ipynb b/notebook/agentchat_nestedchat_optiguide.ipynb index 0b25db8109e..9bcc5e0525f 100644 --- a/notebook/agentchat_nestedchat_optiguide.ipynb +++ b/notebook/agentchat_nestedchat_optiguide.ipynb @@ -9,29 +9,17 @@ } }, "source": [ - "\n", - "\n", - "\n", "# OptiGuide with Nested Chats in AutoGen \n", "\n", "This is a nested chat re-implementation of [OptiGuide](https://github.com/microsoft/OptiGuide), which is an LLM-based supply chain optimization framework. \n", " \n", " \n", - "\\:\\:\\:info Requirements\n", - "\n", "In addition to AutoGen, this notebook also requires eventlet and Gurobipy. The eventlet package is used in this notebook to constrain code execution with a timeout, and the gurobipy package is a mathematical optimization software library for solving mixed-integer linear and quadratic optimization problems. \n", "\n", "```bash\n", "pip install pyautogen eventlet gurobipy\n", "```\n", - "\n", - "For more information, please refer to the [installation guide](/docs/installation/).\n", - "\n", - "\\:\\:\\:" + "\n" ] }, { @@ -62,17 +50,6 @@ "llm_config = {\"config_list\": config_list_gpt4}" ] }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "\\:\\:\\:tip\n", - "\n", - "Learn more about the various ways to configure LLM endpoints [here](/docs/llm_configuration).\n", - "\n", - "\\:\\:\\:" - ] - }, { "cell_type": "markdown", "metadata": {}, From 83aee24942c6ff5dd6527e85f866470a4e587b71 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Sun, 25 Feb 2024 18:59:11 -0500 Subject: [PATCH 54/57] update optiguide notebook --- notebook/agentchat_nestedchat_optiguide.ipynb | 381 +++++++++--------- 1 file changed, 196 insertions(+), 185 deletions(-) diff --git a/notebook/agentchat_nestedchat_optiguide.ipynb b/notebook/agentchat_nestedchat_optiguide.ipynb index 9bcc5e0525f..5e58cb2c462 100644 --- a/notebook/agentchat_nestedchat_optiguide.ipynb +++ b/notebook/agentchat_nestedchat_optiguide.ipynb @@ -24,7 +24,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 11, "metadata": {}, "outputs": [], "source": [ @@ -54,29 +54,23 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "# OptiGuide with Nested Chat" + "# OptiGuide with Nested Chat\n", + "\n", + "Intended agent orchestration in OptiGuide." ] }, { - "attachments": {}, "cell_type": "markdown", "metadata": {}, "source": [ "![](optiGuide_new_design.png)\n", "\n", "\n", - "We set up the foundation for a supply chain coding project, integrating Python code solutions with Gurobi, a popular optimization solver. It accomplishes this through a combination of system messages, utility functions, and code retrieval from an external source. This allows the chatbot to assist users in solving supply chain optimization problems by generating, evaluating, and refining Python code with the Gurobi solver.\n", - "\n", - "### System Messages\n", - "The cell defines several template strings (**WRITER_SYSTEM_MSG**, **SAFEGUARD_SYSTEM_MSG**, etc.) used to guide the interaction between the chatbot (you) and the user. These messages serve multiple purposes:\n", - "\n", - "- **WRITER_SYSTEM_MSG:**
Instructs the chatbot on how to generate and explain Python code solutions for supply chain-related questions, including where to insert the generated code.\n", - "\n", - "- **SAFEGUARD_SYSTEM_MSG:**
Asks the chatbot to determine whether the provided source code is safe to execute, helping to ensure security and privacy.\n", "\n", - "- **Debug and Interpreter Prompts:**
Assist in debugging and interpreting code, guiding the chatbot through resolving errors and presenting execution results in an understandable manner.\n", + "### Step 0. Prepare Helper Functions\n", + "The code cell below includes several helper functions to be used by agents. The helper functions are adopted directly from [OptiGuide](https://github.com/microsoft/OptiGuide).\n", "\n", - "### Utility Functions\n", + "#### Utility Functions\n", "Several utility functions (replace, insert_code, run_with_exec) are defined to manipulate and execute the source code dynamically:\n", "\n", "- **replace(src_code, old_code, new_code) -> str:**
Replaces a specified block of code within the source code with new code. This is essential for updating the code dynamically based on chatbot and user interactions.\n", @@ -85,7 +79,7 @@ "\n", "- **run_with_exec(src_code) -> Union[str, Exception]:**
Executes the modified source code within a controlled environment, capturing and returning the output or any errors that occur. This function is crucial for testing the feasibility and correctness of the generated code solutions.\n", "\n", - "### External Code Retrieval\n", + "#### Functions External Code Retrieval\n", "The cell also includes logic to retrieve example source code from a remote repository using the requests library. This example code serves as a base for the chatbot to work with, allowing users to see real-life applications of the concepts discussed.\n", "\n", "The retrieved code is then displayed, showing both the beginning and the end of the source code to give a glimpse of its structure and content." @@ -93,7 +87,140 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "def replace(src_code: str, old_code: str, new_code: str) -> str:\n", + " \"\"\"\n", + " Inserts new code into the source code by replacing a specified old\n", + " code block.\n", + "\n", + " Args:\n", + " src_code (str): The source code to modify.\n", + " old_code (str): The code block to be replaced.\n", + " new_code (str): The new code block to insert.\n", + "\n", + " Returns:\n", + " str: The modified source code with the new code inserted.\n", + "\n", + " Raises:\n", + " None\n", + "\n", + " Example:\n", + " src_code = 'def hello_world():\\n print(\"Hello, world!\")\\n\\n# Some\n", + " other code here'\n", + " old_code = 'print(\"Hello, world!\")'\n", + " new_code = 'print(\"Bonjour, monde!\")\\nprint(\"Hola, mundo!\")'\n", + " modified_code = _replace(src_code, old_code, new_code)\n", + " print(modified_code)\n", + " # Output:\n", + " # def hello_world():\n", + " # print(\"Bonjour, monde!\")\n", + " # print(\"Hola, mundo!\")\n", + " # Some other code here\n", + " \"\"\"\n", + " pattern = r\"( *){old_code}\".format(old_code=old_code)\n", + " head_spaces = re.search(pattern, src_code, flags=re.DOTALL).group(1)\n", + " new_code = \"\\n\".join([head_spaces + line for line in new_code.split(\"\\n\")])\n", + " rst = re.sub(pattern, new_code, src_code)\n", + " return rst\n", + "\n", + "\n", + "def insert_code(src_code: str, new_lines: str) -> str:\n", + " \"\"\"insert a code patch into the source code.\n", + "\n", + "\n", + " Args:\n", + " src_code (str): the full source code\n", + " new_lines (str): The new code.\n", + "\n", + " Returns:\n", + " str: the full source code after insertion (replacement).\n", + " \"\"\"\n", + " if new_lines.find(\"addConstr\") >= 0:\n", + " return replace(src_code, CONSTRAINT_CODE_STR, new_lines)\n", + " else:\n", + " return replace(src_code, DATA_CODE_STR, new_lines)\n", + "\n", + "\n", + "def run_with_exec(src_code: str) -> Union[str, Exception]:\n", + " \"\"\"Run the code snippet with exec.\n", + "\n", + " Args:\n", + " src_code (str): The source code to run.\n", + "\n", + " Returns:\n", + " object: The result of the code snippet.\n", + " If the code succeed, returns the objective value (float or string).\n", + " else, return the error (exception)\n", + " \"\"\"\n", + " locals_dict = {}\n", + " locals_dict.update(globals())\n", + " locals_dict.update(locals())\n", + "\n", + " timeout = Timeout(\n", + " 60,\n", + " TimeoutError(\"This is a timeout exception, in case \" \"GPT's code falls into infinite loop.\"),\n", + " )\n", + " try:\n", + " exec(src_code, locals_dict, locals_dict)\n", + " except Exception as e:\n", + " return e\n", + " finally:\n", + " timeout.cancel()\n", + "\n", + " try:\n", + " status = locals_dict[\"m\"].Status\n", + " if status != GRB.OPTIMAL:\n", + " if status == GRB.UNBOUNDED:\n", + " ans = \"unbounded\"\n", + " elif status == GRB.INF_OR_UNBD:\n", + " ans = \"inf_or_unbound\"\n", + " elif status == GRB.INFEASIBLE:\n", + " ans = \"infeasible\"\n", + " m = locals_dict[\"m\"]\n", + " m.computeIIS()\n", + " constrs = [c.ConstrName for c in m.getConstrs() if c.IISConstr]\n", + " ans += \"\\nConflicting Constraints:\\n\" + str(constrs)\n", + " else:\n", + " ans = \"Model Status:\" + str(status)\n", + " else:\n", + " ans = \"Optimization problem solved. The objective value is: \" + str(locals_dict[\"m\"].objVal)\n", + " except Exception as e:\n", + " return e\n", + "\n", + " return ans" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Step 1. Agent Construction\n", + "\n", + "This cell introduces the Writer and OptiGuide agent classes and their instances to manage the interaction between the user, the chatbot, and the optimization solver. This streamlines the process of generating, evaluating, and integrating code solutions for supply chain optimization problems.\n", + "\n", + "#### Classes Defined\n", + "\n", + "- **`OptiGuide`**: Inherits from `autogen.AssistantAgent` and serves as the main class for handling the supply chain optimization logic. It maintains state information like the source code, debugging attempts left, success status, and user chat history. Key methods include `set_success` and `update_debug_times`, which are used to update the agent's state based on the outcomes of interactions.\n", + "\n", + "- **`Writer`**: Also inherits from `autogen.AssistantAgent`, this class is tailored to manage the generation and explanation of Python code solutions. It keeps track of the source code and example Q&A to assist in generating responses to user queries.\n", + "\n", + "#### Agent Instances\n", + "\n", + "- **`writer`**, **`safeguard`**, and **`optiguide_commander`**: Instances of the classes defined above, each configured with specific roles in the code generation and evaluation process. These agents work together to ensure that the user's questions are answered with safe, optimized, and understandable Python code solutions.\n", + "\n", + "- **`user`**: An instance of `autogen.UserProxyAgent`, representing the end-user interacting with the notebook. It's configured to simulate user inputs and responses, facilitating an interactive and engaging learning experience.\n", + "\n", + "#### Agent System Messages\n", + "Each agent is associted with a corresponding system message. Those system messages are adopted directly from [OptiGuide](https://github.com/microsoft/OptiGuide).\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 13, "metadata": {}, "outputs": [ { @@ -245,138 +372,8 @@ " execution_result=\"\",\n", ")\n", "safeguard_system_msg = SAFEGUARD_SYSTEM_MSG.format(source_code=code)\n", - "# TODO: system message needs to be changed. Needs to add execution result of the original source code and user_chat_history\n", - "\n", - "\n", - "def replace(src_code: str, old_code: str, new_code: str) -> str:\n", - " \"\"\"\n", - " Inserts new code into the source code by replacing a specified old\n", - " code block.\n", - "\n", - " Args:\n", - " src_code (str): The source code to modify.\n", - " old_code (str): The code block to be replaced.\n", - " new_code (str): The new code block to insert.\n", - "\n", - " Returns:\n", - " str: The modified source code with the new code inserted.\n", "\n", - " Raises:\n", - " None\n", "\n", - " Example:\n", - " src_code = 'def hello_world():\\n print(\"Hello, world!\")\\n\\n# Some\n", - " other code here'\n", - " old_code = 'print(\"Hello, world!\")'\n", - " new_code = 'print(\"Bonjour, monde!\")\\nprint(\"Hola, mundo!\")'\n", - " modified_code = _replace(src_code, old_code, new_code)\n", - " print(modified_code)\n", - " # Output:\n", - " # def hello_world():\n", - " # print(\"Bonjour, monde!\")\n", - " # print(\"Hola, mundo!\")\n", - " # Some other code here\n", - " \"\"\"\n", - " pattern = r\"( *){old_code}\".format(old_code=old_code)\n", - " head_spaces = re.search(pattern, src_code, flags=re.DOTALL).group(1)\n", - " new_code = \"\\n\".join([head_spaces + line for line in new_code.split(\"\\n\")])\n", - " rst = re.sub(pattern, new_code, src_code)\n", - " return rst\n", - "\n", - "\n", - "def insert_code(src_code: str, new_lines: str) -> str:\n", - " \"\"\"insert a code patch into the source code.\n", - "\n", - "\n", - " Args:\n", - " src_code (str): the full source code\n", - " new_lines (str): The new code.\n", - "\n", - " Returns:\n", - " str: the full source code after insertion (replacement).\n", - " \"\"\"\n", - " if new_lines.find(\"addConstr\") >= 0:\n", - " return replace(src_code, CONSTRAINT_CODE_STR, new_lines)\n", - " else:\n", - " return replace(src_code, DATA_CODE_STR, new_lines)\n", - "\n", - "\n", - "def run_with_exec(src_code: str) -> Union[str, Exception]:\n", - " \"\"\"Run the code snippet with exec.\n", - "\n", - " Args:\n", - " src_code (str): The source code to run.\n", - "\n", - " Returns:\n", - " object: The result of the code snippet.\n", - " If the code succeed, returns the objective value (float or string).\n", - " else, return the error (exception)\n", - " \"\"\"\n", - " locals_dict = {}\n", - " locals_dict.update(globals())\n", - " locals_dict.update(locals())\n", - "\n", - " timeout = Timeout(\n", - " 60,\n", - " TimeoutError(\"This is a timeout exception, in case \" \"GPT's code falls into infinite loop.\"),\n", - " )\n", - " try:\n", - " exec(src_code, locals_dict, locals_dict)\n", - " except Exception as e:\n", - " return e\n", - " finally:\n", - " timeout.cancel()\n", - "\n", - " try:\n", - " status = locals_dict[\"m\"].Status\n", - " if status != GRB.OPTIMAL:\n", - " if status == GRB.UNBOUNDED:\n", - " ans = \"unbounded\"\n", - " elif status == GRB.INF_OR_UNBD:\n", - " ans = \"inf_or_unbound\"\n", - " elif status == GRB.INFEASIBLE:\n", - " ans = \"infeasible\"\n", - " m = locals_dict[\"m\"]\n", - " m.computeIIS()\n", - " constrs = [c.ConstrName for c in m.getConstrs() if c.IISConstr]\n", - " ans += \"\\nConflicting Constraints:\\n\" + str(constrs)\n", - " else:\n", - " ans = \"Model Status:\" + str(status)\n", - " else:\n", - " ans = \"Optimization problem solved. The objective value is: \" + str(locals_dict[\"m\"].objVal)\n", - " except Exception as e:\n", - " return e\n", - "\n", - " return ans" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Step 1. Agent Construction\n", - "\n", - "This cell introduces the Writer and OptiGuide agent classes and their instances to manage the interaction between the user, the chatbot, and the optimization solver. This streamlines the process of generating, evaluating, and integrating code solutions for supply chain optimization problems.\n", - "\n", - "### Classes Defined\n", - "\n", - "- **`OptiGuide`**: Inherits from `autogen.AssistantAgent` and serves as the main class for handling the supply chain optimization logic. It maintains state information like the source code, debugging attempts left, success status, and user chat history. Key methods include `set_success` and `update_debug_times`, which are used to update the agent's state based on the outcomes of interactions.\n", - "\n", - "- **`Writer`**: Also inherits from `autogen.AssistantAgent`, this class is tailored to manage the generation and explanation of Python code solutions. It keeps track of the source code and example Q&A to assist in generating responses to user queries.\n", - "\n", - "### Agent Instances\n", - "\n", - "- **`writer`**, **`safeguard`**, and **`optiguide_commander`**: Instances of the classes defined above, each configured with specific roles in the code generation and evaluation process. These agents work together to ensure that the user's questions are answered with safe, optimized, and understandable Python code solutions.\n", - "\n", - "- **`user`**: An instance of `autogen.UserProxyAgent`, representing the end-user interacting with the notebook. It's configured to simulate user inputs and responses, facilitating an interactive and engaging learning experience.\n" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": {}, - "outputs": [], - "source": [ "class OptiGuide(autogen.AssistantAgent):\n", " source_code: str = code\n", " debug_times: int = 3\n", @@ -385,16 +382,6 @@ " success: bool = False\n", " user_chat_history: str = \"\"\n", "\n", - " def set_success(self, success: bool):\n", - " self.success = success\n", - "\n", - " @property\n", - " def get_success(self):\n", - " return self.success\n", - "\n", - " def update_debug_times(self):\n", - " self.debug_times_left -= 1\n", - "\n", "\n", "class Writer(autogen.AssistantAgent):\n", " source_code: str = code\n", @@ -430,7 +417,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 14, "metadata": {}, "outputs": [], "source": [ @@ -459,7 +446,7 @@ "\n", " # safeguard.reset() #TODO: reset safeguard\n", " recipient.debug_times_left = recipient.debug_times\n", - " recipient.set_success(False)\n", + " recipient.success = False\n", " return writer_sys_msg + \"\\n\" + CODE_PROMPT\n", "\n", "\n", @@ -492,7 +479,7 @@ " print(colored(str(execution_rst), \"yellow\"))\n", " if type(execution_rst) in [str, int, float]:\n", " # we successfully run the code and get the result\n", - " sender.set_success(True)\n", + " sender.success = True\n", " # Step 6: request to interpret results\n", " return INTERPRETER_PROMPT.format(execution_rst=execution_rst)\n", " else:\n", @@ -502,7 +489,7 @@ " Please try to find a new way (coding) to answer the question.\"\"\"\n", " if sender.debug_times_left > 0:\n", " # Try to debug and write code again (back to step 2)\n", - " sender.update_debug_times()\n", + " sender.debug_times_left -= 1\n", " return DEBUG_PROMPT.format(error_type=type(execution_rst), error_message=str(execution_rst))\n", "\n", "\n", @@ -513,7 +500,7 @@ "# safeguard is triggered only when receiving a message from the writer\n", "optiguide_commander.register_nested_chats(safeguard_chat_queue, trigger=\"writer\")\n", "# writer is triggered only when receiving a message from the user\n", - "optiguide_commander.register_nested_chats(writer_chat_queue, trigger=\"user\")\n" + "optiguide_commander.register_nested_chats(writer_chat_queue, trigger=\"user\")" ] }, { @@ -525,7 +512,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 15, "metadata": {}, "outputs": [ { @@ -920,18 +907,18 @@ "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0xd9cda048\n", + "Model fingerprint: 0x8aa2c280\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 3280.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 3276.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", @@ -943,7 +930,7 @@ "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 available processors)\n", "\n", - "Solution count 3: 2470 3276 3280 \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", @@ -953,18 +940,18 @@ "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0xd9cda048\n", + "Model fingerprint: 0x8aa2c280\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 3280.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 3276.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", @@ -976,7 +963,7 @@ "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 available processors)\n", "\n", - "Solution count 3: 2470 3276 3280 \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", @@ -986,7 +973,7 @@ "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0xd9cda048\n", + "Model fingerprint: 0x8aa2c280\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", @@ -1001,11 +988,11 @@ "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 available processors)\n", "\n", - "Solution count 3: 2470 3276 3280 \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", - "Sat Feb 24 23:29:59 2024\n", + "Sun Feb 25 18:56:45 2024\n", "Optimal cost: 2470.0\n", "Gurobi Optimizer version 11.0.0 build v11.0.0rc2 (mac64[arm] - Darwin 23.2.0 23C71)\n", "\n", @@ -1013,7 +1000,7 @@ "Thread count: 14 physical cores, 14 logical processors, using up to 14 threads\n", "\n", "Optimize a model with 11 rows, 18 columns and 36 nonzeros\n", - "Model fingerprint: 0xd9cda048\n", + "Model fingerprint: 0x8aa2c280\n", "Variable types: 0 continuous, 18 integer (0 binary)\n", "Coefficient statistics:\n", " Matrix range [1e+00, 1e+00]\n", @@ -1028,11 +1015,11 @@ "Explored 1 nodes (11 simplex iterations) in 0.00 seconds (0.00 work units)\n", "Thread count was 14 (of 14 available processors)\n", "\n", - "Solution count 3: 2470 3276 3280 \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", - "Sat Feb 24 23:29:59 2024\n", + "Sun Feb 25 18:56:45 2024\n", "Optimal cost: 2470.0\n", "\u001b[33mOptimization problem solved. The objective value is: 2470.0\u001b[0m\n", "\u001b[33mcommander\u001b[0m (to writer):\n", @@ -1074,20 +1061,44 @@ "\n", "--------------------------------------------------------------------------------\n" ] - }, + } + ], + "source": [ + "chat_res = user.initiate_chat(\n", + " optiguide_commander, message=\"What if we prohibit shipping from supplier 1 to roastery 2?\"\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Get Final Results from the Returned ChatResult Object " + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [ { - "data": { - "text/plain": [ - "ChatResult(chat_id=None, chat_history=[{'content': 'What if we prohibit shipping from supplier 1 to roastery 2?', 'role': 'assistant'}, {'content': \"Certainly! Here's a human-readable answer:\\n\\nAfter integrating the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was solved with a total cost of $2470.0.\\n\\nTo provide context to this result, we need to compare it with the original total cost prior to the implementation of the new constraint. However, the original total cost was not provided in the information given to me. If the original total cost was known, we would compare the two to understand the financial impact of this constraint on the supply chain.\\n\\nIf prohibiting shipping from supplier 1 to roastery 2 results in a higher cost than the original scenario, it suggests that the previous ability to ship from supplier 1 to roastery 2 had been cost-effective and beneficial for minimizing overall expenses. On the other hand, if the cost is the same or lower, then shipping from supplier 1 to roastery 2 may not have been crucial for the cost optimization of the supply chain, or other routes may have been able to compensate effectively for this restriction.\\n\\nWithout the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\", 'role': 'user'}], summary=\"Certainly! Here's a human-readable answer:\\n\\nAfter integrating the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was solved with a total cost of $2470.0.\\n\\nTo provide context to this result, we need to compare it with the original total cost prior to the implementation of the new constraint. However, the original total cost was not provided in the information given to me. If the original total cost was known, we would compare the two to understand the financial impact of this constraint on the supply chain.\\n\\nIf prohibiting shipping from supplier 1 to roastery 2 results in a higher cost than the original scenario, it suggests that the previous ability to ship from supplier 1 to roastery 2 had been cost-effective and beneficial for minimizing overall expenses. On the other hand, if the cost is the same or lower, then shipping from supplier 1 to roastery 2 may not have been crucial for the cost optimization of the supply chain, or other routes may have been able to compensate effectively for this restriction.\\n\\nWithout the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\", cost=({'total_cost': 0}, {'total_cost': 0}), human_input=[])" - ] - }, - "execution_count": 10, - "metadata": {}, - "output_type": "execute_result" + "name": "stdout", + "output_type": "stream", + "text": [ + "Certainly! Here's a human-readable answer:\n", + "\n", + "After integrating the constraint to prohibit shipping from supplier 1 to roastery 2, the optimization problem was solved with a total cost of $2470.0.\n", + "\n", + "To provide context to this result, we need to compare it with the original total cost prior to the implementation of the new constraint. However, the original total cost was not provided in the information given to me. If the original total cost was known, we would compare the two to understand the financial impact of this constraint on the supply chain.\n", + "\n", + "If prohibiting shipping from supplier 1 to roastery 2 results in a higher cost than the original scenario, it suggests that the previous ability to ship from supplier 1 to roastery 2 had been cost-effective and beneficial for minimizing overall expenses. On the other hand, if the cost is the same or lower, then shipping from supplier 1 to roastery 2 may not have been crucial for the cost optimization of the supply chain, or other routes may have been able to compensate effectively for this restriction.\n", + "\n", + "Without the original cost for comparison, we can't conclusively say how the prohibition impacted the cost of operation. But the new total cost of $2470.0 will be the baseline for future changes or optimizations done following this new restriction.\n" + ] } ], "source": [ - "user.initiate_chat(optiguide_commander, message=\"What if we prohibit shipping from supplier 1 to roastery 2?\")" + "print(chat_res.summary)" ] } ], From b98e4a6d13e04f908d2d24a5147a4c5830a24859 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Mon, 26 Feb 2024 17:17:37 -0500 Subject: [PATCH 55/57] raise value error --- autogen/agentchat/conversable_agent.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py index 470191988e0..b5420510d4d 100644 --- a/autogen/agentchat/conversable_agent.py +++ b/autogen/agentchat/conversable_agent.py @@ -384,8 +384,8 @@ def reply_func_from_nested_chats( """ if reply_func_from_nested_chats == "summary_from_nested_chats": reply_func_from_nested_chats = self._summary_from_nested_chats - assert callable(reply_func_from_nested_chats), "reply_func_from_nested_chats must be a callable" - + if not callable(reply_func_from_nested_chats): + raise ValueError("reply_func_from_nested_chats must be a callable") reply_func = partial(reply_func_from_nested_chats, chat_queue) self.register_reply( trigger, From 136462189597e6c06ba1b9f9a25d05b17dd1c135 Mon Sep 17 00:00:00 2001 From: Qingyun Wu Date: Mon, 26 Feb 2024 17:18:46 -0500 Subject: [PATCH 56/57] update notebook --- notebook/agentchat_nestedchat_optiguide.ipynb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/notebook/agentchat_nestedchat_optiguide.ipynb b/notebook/agentchat_nestedchat_optiguide.ipynb index 5e58cb2c462..8e3ca964dc4 100644 --- a/notebook/agentchat_nestedchat_optiguide.ipynb +++ b/notebook/agentchat_nestedchat_optiguide.ipynb @@ -43,9 +43,9 @@ "\n", "config_list_gpt4 = autogen.config_list_from_json(\n", " \"OAI_CONFIG_LIST\",\n", - " # filter_dict={\n", - " # \"model\": [\"gpt-4\", \"gpt4\", \"gpt-4-32k\", \"gpt-4-32k-0314\", \"gpt-4-32k-v0314\"],\n", - " # },\n", + " filter_dict={\n", + " \"model\": [\"gpt-4\", \"gpt4\", \"gpt-3.5-turbo\" \"gpt-4-32k\", \"gpt-4-32k-0314\", \"gpt-4-32k-v0314\"],\n", + " },\n", ")\n", "llm_config = {\"config_list\": config_list_gpt4}" ] From b976ddb37341584db84e7451575574de379a7957 Mon Sep 17 00:00:00 2001 From: gagb Date: Mon, 26 Feb 2024 23:53:11 +0000 Subject: [PATCH 57/57] Improve language --- notebook/agentchat_nestedchat.ipynb | 47 ++++++++++++++++------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/notebook/agentchat_nestedchat.ipynb b/notebook/agentchat_nestedchat.ipynb index a652ae5adbf..c4a25871e25 100644 --- a/notebook/agentchat_nestedchat.ipynb +++ b/notebook/agentchat_nestedchat.ipynb @@ -12,12 +12,12 @@ "\n", "\n", - "# Nested Chats for Solving Complex Tasks with a Sequence of Inner Monologue\n", + "# Solving Complex Tasks with Nested Chats\n", "\n", - "This notebook showcases how to use nested chats in AutoGen to solve complex task with one or a sequence chats nested as inner monologue.\n", + "This notebook shows how you can leverage \"nested chats\" to solve complex task with AutoGen. Nested chats allow AutoGen agents to use other agents as their inner monologue to accomplish tasks. This abstraction is powerful as it allows you to compose agents in rich ways. This notebook shows how you can nest a pretty complex sequence of chats among _inner_ agents inside an _outer_ agent.\n", "\n", "\\:\\:\\:info Requirements\n", "\n", @@ -59,7 +59,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Example Tasks" + "### Example Task\n", + "\n", + "Suppose we want the agents to complete the following sequence of tasks:" ] }, { @@ -78,12 +80,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Since the first task could be complex to solve, we want to construct additional agents as an inner monologue, i.e., nested chats.\n", + "Since the first task could be complex to solve, lets construct new agents that can serve as an inner monologue.\n", "\n", - "### Step 1. Agent Construction\n", + "### Step 1. Define Agents\n", "\n", - "#### Construction of a Group Chat to Be Used as an Inner Monologue\n", - "In the following code block, we construct a group chat manager which manages an inner_assistant agent and an inner_code_interpreter agent.\n" + "#### A Group Chat for Inner Monologue\n", + "Below, we construct a group chat manager which manages an `inner_assistant` agent and an `inner_code_interpreter` agent. \n", + "Later we will use this group chat inside another agent.\n" ] }, { @@ -132,8 +135,9 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "#### Construction of Individual Agents to Be Used in the Agent System\n", - "\n" + "#### Inner- and Outer-Level Individual Agents\n", + "\n", + "Now we will construct a number of individual agents that will assume role of outer and inner agents." ] }, { @@ -145,14 +149,13 @@ "assistant_1 = autogen.AssistantAgent(\n", " name=\"Assistant_1\",\n", " llm_config={\"config_list\": config_list},\n", - " # is_termination_msg=lambda x: x.get(\"content\", \"\") == \"\",\n", ")\n", "\n", "assistant_2 = autogen.AssistantAgent(\n", " name=\"Assistant_2\",\n", " llm_config={\"config_list\": config_list},\n", - " # is_termination_msg=lambda x: x.get(\"content\", \"\") == \"\",\n", ")\n", + "\n", "writer = autogen.AssistantAgent(\n", " name=\"Writer\",\n", " llm_config={\"config_list\": config_list},\n", @@ -192,29 +195,31 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Step 2: Orchestrate a Multi-Agent System with Nested Chats to Solve Tasks\n", + "### Step 2: Orchestrate Nested Chats to Solve Tasks\n", "\n", + "#### Outer Level\n", "In the following code block, at the outer level, we have communication between:\n", "\n", - "- user - assistant_1 for solving the first task, i.e., tasks[0].\n", - "- user - assistant_2 for solving the second task, i.e., tasks[1].\n", + "- `user` - `assistant_1` for solving the first task, i.e., `tasks[0]`.\n", + "- `user` - `assistant_2` for solving the second task, i.e., `tasks[1]`.\n", "\n", - "Since the first task is quite complicated, we created a sequence of nested chats as the inner monologue of Assistant_1.\n", + "#### Inner Level (Nested Chats)\n", + "Since the first task is quite complicated, we created a sequence of _nested chats_ as the inner monologue of Assistant_1.\n", "\n", - "- Nested Chat 1: assistant_1 - manager. This chat intends to delegate the task received by Assistant_1 to the Manager to solve.\n", + "1. `assistant_1` - `manager`: This chat intends to delegate the task received by Assistant_1 to the Manager to solve.\n", "\n", - "- Nested Chat 2: assistant_1 - writer. This chat takes the output from Nested Chat 1, i.e., Assistant_1 vs. Manager, and lets the Writer polish the content to make an engaging and nicely formatted blog post, which is realized through the writing_message function.\n", + "2. `assistant_1` - `writer`: This chat takes the output from Nested Chat 1, i.e., Assistant_1 vs. Manager, and lets the Writer polish the content to make an engaging and nicely formatted blog post, which is realized through the writing_message function.\n", "\n", - "- Nested Chat 3: assistant_1 - reviewer. This chat takes the output from Nested Chat 2 and intends to let the Reviewer agent review the content from Nested Chat 2.\n", + "3. `assistant_1` - `reviewer`: This chat takes the output from Nested Chat 2 and intends to let the Reviewer agent review the content from Nested Chat 2.\n", "\n", - "- Nested Chat 4: assistant_1 - writer. This chat takes the output from previous nested chats and intends to let the Writer agent finalize a blog post.\n", + "4. `assistant_1` - `writer`: This chat takes the output from previous nested chats and intends to let the Writer agent finalize a blog post.\n", "\n", "The sequence of nested chats can be realized with the `register_nested_chats` function, which allows one to register one or a sequence of chats to a particular agent (in this example, the `assistant_1` agent).\n", "\n", "Information about the sequence of chats can be specified in the `chat_queue` argument of the `register_nested_chats` function. The following fields are especially useful:\n", "- `recipient` (required) specifies the nested agent;\n", "- `message` specifies what message to send to the nested recipient agent. In a sequence of nested chats, if the `message` field is not specified, we will use the last message the registering agent received as the initial message in the first chat and will skip any subsequent chat in the queue that does not have the `message` field. You can either provide a string or define a callable that returns a string.\n", - "- `summary_method` decides what to get out of the nested chat. You can either select from existing options including \"last_msg\" and \"reflection_with_llm\", or or define your own way on what to get from the nested chat with a Callable.\n", + "- `summary_method` decides what to get out of the nested chat. You can either select from existing options including `\"last_msg\"` and `\"reflection_with_llm\"`, or or define your own way on what to get from the nested chat with a Callable.\n", "- `max_turns` determines how many turns of conversation to have between the concerned agent pairs." ] },