autogen/notebook/agentchat_compression.ipynb

656 lines
58 KiB
Plaintext

{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"<a href=\"https://colab.research.google.com/github/microsoft/autogen/blob/main/notebook/agentchat_compression.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Auto Generated Agent Chat: Convesations with Chat History Compression Enabled (Experimental)\n",
"\n",
"AutoGen offers conversable agents powered by LLM, tools, or humans, which can be used to perform tasks collectively via automated chat. This framework allows tool use and human participance through multi-agent conversation. Please find documentation about this feature [here](https://microsoft.github.io/autogen/docs/Use-Cases/agent_chat).\n",
"\n",
"In this notebook, we demonstrate how to enable compression of history messages for `AssistantAgent`. By setting `compress_config` when initializing an `AssistantAgent`, you can turn on/off the compression feature. By default (`compress_config=False`), compression is disabled and the originally functionality of `AssistantAgent` is preserved. Several cases with compression:\n",
"- Case 1: Initialize with `compress_config=True`, compression is enabled with default settings.\n",
"- Case 2: Initialize with dict `compress_config={\"mode\": \"COMPRESS\", \"trigger_count\": <your pre-set number>}`: compression is enabled, with trigger_count set to your pre-set number.\n",
"- Case 3: Initialize with dict `compress_config={\"mode\": \"TERMINATE\"}`: no compression will be performed. However, we will count token usages before sending requests to the OpenAI model. The conversation will be terminated directly if the total token usage exceeds the maximum token usage allowed by the model (to avoid the token limit error from OpenAI API).\n",
"- Case 4: Initialize with dict `compress_config={\"mode\": \"COMPRESS\", \"agent\": <A customized agent for compression>, \"trigger_count\" : <your pre-set amount>}`: the `generate_reply` function from your customized agent will be called on trigger count and the return is assumed to new set of messages after compression.\n",
"\n",
"By adjusting `trigger_count`, you can decide when to compress the history messages based on existing tokens. (Default: 0.7):\n",
"1. If all history messages have more than 600 tokens, compress them: {\"mode\": \"COMPRESS\", \"trigger_count\": 600}\n",
"2. If this is a float number between 0 and 1, it is interpreted as ratio of max tokens allowed by the model. For example the AssistantAgent uses gpt-4 with max tokens 8192, the trigger_count = 0.7 * 8192 = 5734.4: `compress_config = {\"mode\": \"COMPRESS\", \"trigger_count\": 0.7}`.\n",
"\n",
"Currently, our compression logic (from Case 1 and 2) is as follows:\n",
"1. We will leave the first user message (as well as system prompts) and compress the rest of the history messages.\n",
"2. The summary is performed in a per-message basis, with the role of the messages (See compressed content in example below).\n",
"\n",
"## Limitations\n",
"- **For now, the compression feature is not well supported for groupchat**. If you initialize an `AssistantAgent` in a groupchat with compression, the compressed cannot be broadcast to all other agents in the groupchat. If you will to use this feature in groupchat, extra cost will be incurred since compression will be performed at per-agent basis.\n",
"- We do not support async compression for now.\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": [
"# %pip install pyautogen~=0.1.0"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Set your API Endpoint\n",
"\n",
"The [`config_list_from_json`](https://microsoft.github.io/autogen/docs/reference/oai/openai_utils#config_list_from_json) function loads a list of configurations from an environment variable or a json file.\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import autogen\n",
"\n",
"config_list = autogen.config_list_from_json(\n",
" \"OAI_CONFIG_LIST\",\n",
" filter_dict={\n",
" \"model\": [\"gpt-4\", \"gpt-4-0314\", \"gpt4\", \"gpt-4-32k\", \"gpt-4-32k-0314\", \"gpt-4-32k-v0314\"],\n",
" },\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"It first looks for environment variable \"OAI_CONFIG_LIST\" which needs to be a valid json string. If that variable is not found, it then looks for a json file named \"OAI_CONFIG_LIST\". It filters the configs by models (you can filter by other keys as well).\n",
"\n",
"The config list looks like the following:\n",
"```python\n",
"config_list = [\n",
" {\n",
" 'model': 'gpt-4',\n",
" 'api_key': '<your OpenAI API key here>',\n",
" },\n",
" {\n",
" 'model': 'gpt-4',\n",
" 'api_key': '<your Azure OpenAI API key here>',\n",
" 'api_base': '<your Azure OpenAI API base here>',\n",
" 'api_type': 'azure',\n",
" 'api_version': '2023-06-01-preview',\n",
" },\n",
" {\n",
" 'model': 'gpt-3.5-turbo',\n",
" 'api_key': '<your Azure OpenAI API key here>',\n",
" 'api_base': '<your Azure OpenAI API base here>',\n",
" 'api_type': 'azure',\n",
" 'api_version': '2023-06-01-preview',\n",
" },\n",
"]\n",
"```\n",
"\n",
"If you open this notebook in colab, you can upload your files by clicking the file icon on the left panel and then choose \"upload file\" icon.\n",
"\n",
"You can set the value of config_list in other ways you prefer, e.g., loading from a YAML file."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Example 1"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[33mmathproxyagent\u001b[0m (to assistant):\n",
"\n",
"Let's use Python to solve a math problem.\n",
"\n",
"Query requirements:\n",
"You should always use the 'print' function for the output and use fractions/radical forms instead of decimals.\n",
"You can use packages like sympy to help you.\n",
"You must follow the formats below to write your code:\n",
"```python\n",
"# your code\n",
"```\n",
"\n",
"First state the key idea to solve the problem. You may choose from three ways to solve the problem:\n",
"Case 1: If the problem can be solved with Python code directly, please write a program to solve it. You can enumerate all possible arrangements if needed.\n",
"Case 2: If the problem is mostly reasoning, you can solve it by yourself directly.\n",
"Case 3: If the problem cannot be handled in the above two ways, please follow this process:\n",
"1. Solve the problem step by step (do not over-divide the steps).\n",
"2. Take out any queries that can be asked through Python (for example, any calculations or equations that can be calculated).\n",
"3. Wait for me to give the results.\n",
"4. Continue if you think the result is correct. If the result is invalid or unexpected, please correct your query or reasoning.\n",
"\n",
"After all the queries are run and you get the answer, put the answer in \\boxed{}.\n",
"\n",
"Problem:\n",
"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\n",
"\n",
"--------------------------------------------------------------------------------\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[33massistant\u001b[0m (to mathproxyagent):\n",
"\n",
"The key idea to solve the problem is to simplify the inequality expressions, find out when the inequality is equal to zero, check the sign of the inequality, and finally state the solution in an interval notation. \n",
"\n",
"We will use sympy in Python to solve this problem.\n",
"\n",
"Here are the steps:\n",
"1. Rewrite the inequality as $(2x + 10)(x + 3) - (3x + 9)(x + 8) < 0$.\n",
"2. Simplify the expression.\n",
"3. Find the roots of the simplified expression.\n",
"4. Use the roots to divide the x-axis into intervals.\n",
"5. Select a value from each interval to check whether the inequality is true.\n",
"6. Write down the solution in interval notation.\n",
"\n",
"Let's begin. \n",
"\n",
"We will use sympy to simplify the inequality and find the roots.\n",
"\n",
"```python\n",
"from sympy import symbols, solve, simplify\n",
"\n",
"x = symbols('x')\n",
"\n",
"# Rewrite the inequality as f(x) = 0\n",
"f = (2*x + 10)*(x + 3) - (3*x + 9)*(x + 8)\n",
"\n",
"# Simplify the expression\n",
"f_simplified = simplify(f)\n",
"\n",
"# Find the roots of the equation\n",
"roots = solve(f_simplified, x)\n",
"\n",
"# Print simplified function and roots\n",
"print('Simplified function:', f_simplified)\n",
"print('Roots of the function:', roots)\n",
"```\n",
"\n",
"--------------------------------------------------------------------------------\n",
"\u001b[33mmathproxyagent\u001b[0m (to assistant):\n",
"\n",
"Simplified function: (-x - 14)*(x + 3)\n",
"Roots of the function: [-14, -3]\n",
"\n",
"--------------------------------------------------------------------------------\n",
"\u001b[35m******************************Start compressing the following content:******************************\u001b[0m\n",
"To be compressed:\n",
"##ASSISTANT## The key idea to solve the problem is to simplify the inequality expressions, find out when the inequality is equal to zero, check the sign of the inequality, and finally state the solution in an interval notation. \n",
"\n",
"We will use sympy in Python to solve this problem.\n",
"\n",
"Here are the steps:\n",
"1. Rewrite the inequality as $(2x + 10)(x + 3) - (3x + 9)(x + 8) < 0$.\n",
"2. Simplify the expression.\n",
"3. Find the roots of the simplified expression.\n",
"4. Use the roots to divide the x-axis into intervals.\n",
"5. Select a value from each interval to check whether the inequality is true.\n",
"6. Write down the solution in interval notation.\n",
"\n",
"Let's begin. \n",
"\n",
"We will use sympy to simplify the inequality and find the roots.\n",
"\n",
"```python\n",
"from sympy import symbols, solve, simplify\n",
"\n",
"x = symbols('x')\n",
"\n",
"# Rewrite the inequality as f(x) = 0\n",
"f = (2*x + 10)*(x + 3) - (3*x + 9)*(x + 8)\n",
"\n",
"# Simplify the expression\n",
"f_simplified = simplify(f)\n",
"\n",
"# Find the roots of the equation\n",
"roots = solve(f_simplified, x)\n",
"\n",
"# Print simplified function and roots\n",
"print('Simplified function:', f_simplified)\n",
"print('Roots of the function:', roots)\n",
"```\n",
"##USER## Simplified function: (-x - 14)*(x + 3)\n",
"Roots of the function: [-14, -3]\n",
"\n",
"\u001b[35m******************************Content after compressing: (type=<class 'str'>)******************************\u001b[0m\n",
"##ASSISTANT## To solve the inequality, we need to simplify the inequality, identify when it equals zero, verify the sign of the inequality, and write the solution in interval notation using sympy in Python. \n",
"Steps involve: \n",
"1. Rewriting the inequality in a form \n",
"2. Simplifying the expression \n",
"3. Identifying the roots \n",
"4. Dividing the x-axis into intervals using the roots \n",
"5. Checking each interval to see if inequality is true \n",
"6. Writing the solution in interval notation.\n",
"\n",
"##CODE## Sympy is used to simplify the inequality and find the roots of the inequality $(2x + 10)(x + 3) - (3x + 9)(x + 8) < 0$.\n",
"\n",
"##USER## The simplified function is $(-x - 14)*(x + 3)$ and its roots are [-14, -3]. \u001b[35m\n",
"********************************************************************************\u001b[0m\n",
"\u001b[35mToken Count (of msgs after first prompt): Before compression: 324 After: 209 | Total prompt token count after compression: 523\u001b[0m\n",
"--------------------------------------------------------------------------------\n",
"\u001b[33massistant\u001b[0m (to mathproxyagent):\n",
"\n",
"Great, now we have the roots -14 and -3.\n",
"\n",
"Given these roots, we have divided the x-axis into three intervals: (-inf, -14), (-14, -3), (-3, inf). \n",
"\n",
"We will select one value from each interval and check whether the inequality (-x - 14)*(x + 3) < 0 holds true. \n",
"\n",
"As we are working with inequalities, remember:\n",
"1. If the inequality is strictly < or >, then we should not include the points at which the inequality equals zero (i.e., roots) in the solution. These points should be shown with parentheses.\n",
"\n",
"2. If the inequality is ≤ or ≥, then we do include the points where it equals zero, which should be denoted with square brackets.\n",
"\n",
"Let's calculate:\n",
"\n",
"```python\n",
"from sympy import oo\n",
"\n",
"# Define intervals\n",
"intervals = [(-oo, -14), (-14, -3), (-3, oo)]\n",
"\n",
"solutions = []\n",
"\n",
"# Check within each interval\n",
"for interval in intervals:\n",
" # Get a test point in this interval \n",
" test_point = sum(interval) / 2\n",
"\n",
" # Check whether the inequality holds true for this test point\n",
" if f_simplified.subs(x, test_point) < 0:\n",
" solutions.append(interval)\n",
"\n",
"# Print solutions\n",
"print('Solution intervals:', solutions)\n",
"```\n",
"\n",
"--------------------------------------------------------------------------------\n",
"\u001b[33mmathproxyagent\u001b[0m (to assistant):\n",
"\n",
"Solution intervals: [(-oo, -14), (-3, oo)]\n",
"\n",
"--------------------------------------------------------------------------------\n",
"\u001b[35m******************************Start compressing the following content:******************************\u001b[0m\n",
"To be compressed:\n",
"##ASSISTANT## To solve the inequality, we need to simplify the inequality, identify when it equals zero, verify the sign of the inequality, and write the solution in interval notation using sympy in Python. \n",
"Steps involve: \n",
"1. Rewriting the inequality in a form \n",
"2. Simplifying the expression \n",
"3. Identifying the roots \n",
"4. Dividing the x-axis into intervals using the roots \n",
"5. Checking each interval to see if inequality is true \n",
"6. Writing the solution in interval notation.\n",
"\n",
"##CODE## Sympy is used to simplify the inequality and find the roots of the inequality $(2x + 10)(x + 3) - (3x + 9)(x + 8) < 0$.\n",
"\n",
"##USER## The simplified function is $(-x - 14)*(x + 3)$ and its roots are [-14, -3].\n",
"##ASSISTANT## Great, now we have the roots -14 and -3.\n",
"\n",
"Given these roots, we have divided the x-axis into three intervals: (-inf, -14), (-14, -3), (-3, inf). \n",
"\n",
"We will select one value from each interval and check whether the inequality (-x - 14)*(x + 3) < 0 holds true. \n",
"\n",
"As we are working with inequalities, remember:\n",
"1. If the inequality is strictly < or >, then we should not include the points at which the inequality equals zero (i.e., roots) in the solution. These points should be shown with parentheses.\n",
"\n",
"2. If the inequality is ≤ or ≥, then we do include the points where it equals zero, which should be denoted with square brackets.\n",
"\n",
"Let's calculate:\n",
"\n",
"```python\n",
"from sympy import oo\n",
"\n",
"# Define intervals\n",
"intervals = [(-oo, -14), (-14, -3), (-3, oo)]\n",
"\n",
"solutions = []\n",
"\n",
"# Check within each interval\n",
"for interval in intervals:\n",
" # Get a test point in this interval \n",
" test_point = sum(interval) / 2\n",
"\n",
" # Check whether the inequality holds true for this test point\n",
" if f_simplified.subs(x, test_point) < 0:\n",
" solutions.append(interval)\n",
"\n",
"# Print solutions\n",
"print('Solution intervals:', solutions)\n",
"```\n",
"##USER## Solution intervals: [(-oo, -14), (-3, oo)]\n",
"\n",
"\u001b[35m******************************Content after compressing: (type=<class 'str'>)******************************\u001b[0m\n",
"##ASSISTANT## Outlined steps to solve the given inequality: simplifying the equation, finding its roots, dividing the x-axis into intervals using the roots, and checking each interval's validity for the inequality.\n",
"##CODE## Sympy was used to simplify the inequality $(2x + 10)(x + 3) - (3x + 9)(x + 8) < 0$.\n",
"##USER## Identified simplified function as $(-x - 14)*(x + 3)$ with roots as [-14, -3].\n",
"##ASSISTANT## Divided x-axis into three intervals by the roots [-14,-3]. Explained concept of not including inequality's roots while testing the truth value of each interval using sympy. \n",
"##CODE## Implemented checking the truth value of each interval using sympy, found the solution intervals.\n",
"##USER## Reported solution intervals as [(-oo, -14), (-3, oo)]. \u001b[35m\n",
"********************************************************************************\u001b[0m\n",
"\u001b[35mToken Count (of msgs after first prompt): Before compression: 509 After: 219 | Total prompt token count after compression: 533\u001b[0m\n",
"--------------------------------------------------------------------------------\n",
"\u001b[33massistant\u001b[0m (to mathproxyagent):\n",
"\n",
"Excellent! \n",
"\n",
"Therefore, the complete solution to the inequality is $x \\in (-\\infty, -14) \\cup (-3, \\infty)$.\n",
"\n",
"So, all x that satisfy the inequality are those x in $(-\\infty, -14)$ and $(-3, \\infty)$.\n",
"\n",
"So, we have our answer as:\n",
"\\[ \\boxed{x \\in (-\\infty, -14) \\cup (-3, \\infty)} \\].\n",
"\n",
"--------------------------------------------------------------------------------\n"
]
}
],
"source": [
"import autogen\n",
"autogen.ChatCompletion.start_logging()\n",
"from autogen.agentchat.contrib.math_user_proxy_agent import MathUserProxyAgent\n",
"\n",
"# 1. create an AssistantAgent instance named \"assistant\"\n",
"assistant = autogen.AssistantAgent(\n",
" name=\"assistant\", \n",
" system_message=\"You are a helpful assistant.\",\n",
" llm_config={\n",
" \"request_timeout\": 600,\n",
" \"seed\": 43,\n",
" \"config_list\": config_list,\n",
" },\n",
" compress_config={\n",
" \"mode\": \"COMPRESS\",\n",
" \"trigger_count\": 600, # set this to a large number for less frequent compression\n",
" }\n",
")\n",
"\n",
"# 2. create the MathUserProxyAgent instance named \"mathproxyagent\"\n",
"# By default, the human_input_mode is \"NEVER\", which means the agent will not ask for human input.\n",
"mathproxyagent = MathUserProxyAgent(\n",
" name=\"mathproxyagent\", \n",
" human_input_mode=\"NEVER\",\n",
" code_execution_config={\"use_docker\": False},\n",
" max_consecutive_auto_reply=5,\n",
")\n",
"math_problem = \"Find all $x$ that satisfy the inequality $(2x+10)(x+3)<(3x+9)(x+8)$. Express your answer in interval notation.\"\n",
"mathproxyagent.initiate_chat(assistant, problem=math_problem)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Example 2"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[33muser_proxy\u001b[0m (to chatbot):\n",
"\n",
"Draw two agents chatting with each other with an example dialog. Don't add plt.show().\n",
"\n",
"--------------------------------------------------------------------------------\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[33mchatbot\u001b[0m (to user_proxy):\n",
"\n",
"\u001b[32m***** Suggested function Call: python *****\u001b[0m\n",
"Arguments: \n",
"{\n",
" \"cell\": \"import matplotlib.pyplot as plt\\n\n",
"plt.figure(figsize=(10, 5))\\n\n",
"\n",
"# Representing two agents\\n\n",
"plt.text(0.2, 0.4, 'Agent 1', ha='center', va='center', size=20, bbox=dict(boxstyle=\\\"rarrow\\\", fc=\\\"w\\\"))\\n\n",
"plt.text(0.8, 0.6, 'Agent 2', ha='center', va='center', size=20, bbox=dict(boxstyle=\\\"larrow\\\", fc=\\\"w\\\"))\\n\n",
"\n",
"# Dialog lines\\n\n",
"plt.plot([0.3, 0.7], [0.4, 0.6], 'k-')\\n\n",
"\n",
"# Dialog\\n\n",
"plt.text(0.5, 0.5, '\\\"Hello!\\\"', ha='center', va='center', rotation=20)\\n\n",
"plt.text(0.5, 0.5, '\\\"Hi!\\\"', ha='center', va='center', rotation=-15)\\n\n",
"\n",
"# Hide axes\\n\n",
"plt.axis('off')\"\n",
"}\n",
"\u001b[32m*******************************************\u001b[0m\n",
"\n",
"--------------------------------------------------------------------------------\n",
"\u001b[35m\n",
">>>>>>>> EXECUTING FUNCTION python...\u001b[0m\n"
]
},
{
"data": {
"text/plain": [
"(0.27999999999999997, 0.72, 0.39, 0.61)"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
},
{
"data": {
"image/png": "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kiTppKV0gDp06BCvvfYaAI0aNfpLsMmfPz833XQTcPi+UcnJyX85xvz589m+fTvw1zOp/qxIkSLUr1//uI+///77AFSsWJFLL730hLuvvfZaAL755pvj3qT80ksv5bLLLjvmY1FRUVxxxRUArFy58oTvda7NnDmTbt26AVC4cGGeffbZ0zqOEUqSJEmSlOrt37+fkSNH8uijjwY9JUNL6QAF8Mknn0Tu43S8gHTk+0d2/a+lS5dGvv67M3iuuuqq4z42f/584PCn2f3vp9H9718PPfQQAImJiWzduvWYx6tUqdIJt+TPnx+AXbt2nfB559L3339PkyZNOHToEDly5OCtt96icOHCp3UsI5QkSZIkKdXav38/w4cPp2zZsjz00EM888wzLFu2LOhZGdZ7773HokWLGDlyZIoEKPh/l9gVKFCABg0aHPM5DRs25IILLjjq+X+2bdu2yNeFChU64fud6PGNGzf+3dxj2rt37zG/nytXrhO+LlOmw9kmKSnptN73TP3666/ceOONbNu2jcyZM/P6669HzvA6Hd6YXJIkSZKU6uzbt4/nn3+e/v37s27dOgBKlixJ9+7dKVu2bMDrMq5bb72VESNG0LFjRy655JJzHqJ27NgRuQRuy5YtkXs/nci7777LqFGjOO+88876niMxqGrVqrzyyisn/bojn+aXlqxdu5a6deuydu1aoqKieOmll2jcuPEZHdMIJUmSJElKNfbt28fo0aPp378/69evB6BUqVI89dRTtG3bluzZswe8MGMrUKAA06ZNo27dulx//fXn/JK8N998k/3795/Sa3bv3s27775L69atI9/Lly9f5OtNmzZRoUKF475+06ZNx32sQIECkfdIqTPBgrB582bq1asXuRfV8OHDadOmzRkf1wglSZIkSQrc3r17ee655xgwYEDk/j8XXXRRJD6dzBkwShkpGaKOXFpXrFgxhgwZ8rfP79KlC2vWrGH8+PFHRajKlStHvl6wYAE1a9Y87jGO3PfpWK644gpmz57NypUrWb9+PUWLFj2ZH+OciIqKOifH3bFjB/Xr149c9tqvXz86dux4Vo5thJIkSZIkBWbPnj08++yzDBw4MHK/ndKlS9OjRw/atGljfEqlUiJE/frrr8yaNQuApk2b0qJFi799zddff82wYcOYPn06f/zxR+QyuKuuuoq8efOyY8cOXnnlFR555JFjvn7Dhg188sknxz3+LbfcwsiRIwmHwwwbNoy+ffuexk92duTIkSPy9YEDB87KMffu3cvNN9/Mt99+C0CPHj3o2rXrWTk2eGNySZIkSVIA9uzZw8CBAylTpgxdunRh48aNlClThjFjxvDTTz/Rvn17A1QqdyRElShRguuvv/6oT6A7G8aPH084HAagWbNmJ/WaI89LTk4+6p5NOXLkiFxO9s033zBs2LC/vDY5OZn77rvvhJf/3XjjjVSvXh2AgQMH8uabb55wz5IlS5gyZcpJbT9VBQoUiPx35Jdffjnj4x08eJAmTZpEwl+nTp3o1avXGR/3z6LCR/4vKkmSJEnSObZ7925GjhzJoEGD2Lx5MwBly5alR48etGrViqxZswa8UKdqy5Yt1K1blzVr1pzVM6LKlSvHL7/8QuHChVm3bl3kk+JOJDk5mRIlSrBu3ToqV658VBjbunUrlStXjtxrrHXr1tx1110UKlSIFStWMGzYMGbPnk316tWZN28eAKtWreKiiy466j1++eUXqlevztatWwFo1KgRzZs3p3z58mTOnJmNGzeycOFCpkyZwtdff03nzp0ZNGjQUccoXbo0v/32G3fffTdjx4497s/Ttm1bxo0bx0UXXcSqVav+8vi//vUvZs2aRYECBRg+fDiXX3555L9D+fPnJ3/+/H/7e3ZE06ZNeffddwGoU6cOQ4cOPeElf9myZTvhvbX+rFChQjz++ONejidJkiRJOvd27drFiBEjGDx4MFu2bAEOR4bo6GjuuususmTxj6dp1bm4NG/WrFmRs3uaNGlyUgEKIFOmTDRp0oRRo0bx/fffs2DBAqpVqwYcjjIff/wx9erVY9OmTUyYMIEJEyYc9fq2bdtSq1atSIT68yVvR5QtW5Y5c+bQtGlTli5dypQpU054tlOePHlOavvp6N69O40aNWLLli20bNnyqMfi4uKIj48/6WMdCVAA06dP57LLLjvh848Xxk7Ey/EkSZIkSefMzp076dOnD6VLl+app55iy5YtlC9fnvHjx7N8+XLuvvtuA1Q6cLYvzTtyQ3I4fIbOqfjz8/98HICqVauybNkyOnfuTPny5cmePTsFCxbk+uuv57XXXuPll19m586dkefnzZv3mO9RoUIFFi1axGuvvUbTpk0pVaoUOXPmJFu2bBQrVozatWsTHR3NggUL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JCnVSkpK4q233qJnz54sW7YMgLx58/Loo4/SqVMn8uXLF/BCSdLJMkJJkiRJSnWSkpJ488036dmzJ8uXLwfgggsuiMSnCy64INiBkqRTZoSSJEmSlGokJSXx+uuv06tXL3744QcA8uXLx2OPPcYjjzxC3rx5A14oSTpdRihJkiRJgTt06BATJ06kV69e/PTTTwDkz5+fxx9/nIcffpg8efIEvFCSdKaMUJIkSZICc+jQIV577TV69erFzz//DByOT507d+ahhx4yPklSOmKEkiRJkpTiDh06xCuvvELv3r1ZsWIFAAUKFOCJJ56gY8eOnH/++QEvlCSdbUYoSZIkSSkmMTGRCRMm0Lt3b1auXAlAwYIF6dKlCw8++CC5c+cOeKEk6VwxQkmSJEk65xITExk/fjy9e/fm119/BaBQoUJ06dKFBx54wPgkSRmAEUqSJEnSOXPw4EHGjRtHnz59WLVqFQCFCxfmySef5P777+e8884LdqAkKcUYoSRJkiSddQcPHuTll1+mb9++/PbbbwAUKVKErl27ct9995ErV66AF0qSUpoRSpIkSdJZc+DAAV5++WX69OnD6tWrAShatChdu3bl3nvvNT5JUgZmhJIkSZJ0xg4cOMCYMWPo27cva9asAaBYsWJ069aNDh06kDNnzoAXSpKCZoSSJEmSdNr279/Piy++SL9+/fjjjz8AuPDCC+nWrRvt27cnR44cAS+UJKUWRihJkiRJp2z//v288MIL9OvXj7Vr1wJQokQJunXrxj333GN8kiT9hRFKkiRJ0knbt28fzz//PP3792fdunUAlCxZku7du/Of//yH7NmzB7xQkpRaGaEkSZIk/a29e/cyevRoBgwYwPr16wEoVaoUTz31FG3btjU+SZL+lhFKkiRJ0nHt3buX5557jgEDBrBhwwYALrrookh8ypYtW8ALJUlphRFKkiRJ0l/s2bOHZ599loEDB7Jx40YASpcuTY8ePWjTpo3xSZJ0yoxQkiRJkiJ2797NqFGjGDRoEJs2bQKgTJkyREdH07p1a7JmzRrwQklSWmWEkiRJksTu3bsZOXIkgwYNYvPmzQCULVuWHj160KpVK+OTJOmMGaEkSZKkDGzXrl2MGDGCwYMHs2XLFgDKlStHdHQ0d911F1my+EcGSdLZ4T9RJEmSpAxo586dDB8+nCFDhrB161YAypcv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6dKlefXVVwEoUqQIb7/9Nt999x233357JD4duWm5JElSeuSn40mSpAwrHA4zdepUcuXKxUUXXUTp0qXZuXMnv/76K1WrVmX+/PlUrVqVnTt30q5dO3Lnzs3WrVvZuXMnhQsXBuDyyy9n+fLlDBkyhEmTJjF48GCaNWvG+eefT+/evUlMTCQpKQmAqlWrctFFFzFv3jx++eUXcuXKFeSPL0mSlKK8J5QkScqwoqKiqF+/fuTXy5cvJ3fu3EyfPp2SJUty5ZVXApAzZ06qVKnC2LFjKVOmDNdddx0FChTg+eefZ8CAAZQrV44tW7YwZswY6tWrx4EDBxg5ciT79+8HoGDBgowePZpbb72VJUuWEB8fz6FDhwL5mSVJkoJihJIkSRlacnJy5HK4ihUrAjBnzhxWr17NddddR/369WnYsCH16tVjxIgRrF+/nnLlylG6dGmyZ89OXFwcL7/8Mt9++y0rVqzg+++/58CBA8Dhs6Quvvhifv31V2rVqkWmTJmoWrUqEyZM4IcffuCqq64K7OeWJElKad4TSpIkZUjJyckAf7kZ+M6dO/n444+Jj4/n5ptvZtq0aVx//fU0b96cgwcPsm3bNi655BIqV65MpkyZePvtt2nfvj2NGzdm586dHDhwgMyZMzNixAi+/fZb6tevz9q1a2nXrh0AK1eupFevXlSvXp0tW7ak+M8tSZIUFCOUJEnKkI7EpyP3azpym8zFixezZ88eqlSpwoUXXsj48eNZsWIFr732Grly5WLXrl2UKFGC5ORkVq9ezcGDB5kwYQL79u0jb9685MuXj2uvvZa77rqLqKgodu7cyYYNGyhWrBh//PEHbdu2JW/evDz++ONkz549sJ9fkiQppRmhJElSuna8z2BZtmwZLVu25N133z3qeSNHjiRz5sx06NCBMWPGUKVKFT799FMqVapE0aJFyZYtG3fffTelS5eOnMlUuHBh/vvf//Luu++yfft29u7dy7Zt2/j555957733yJMnD82bN2fhwoUUL16cevXqMWjQIM4777yU+U2QJElKBbwnlCRJSpfC4TBRUVFERUUd8/t58uRh8eLFlC5dmttvv51MmTKRmJjI+++/zyWXXMKCBQuIiopiwIABDBw4kBkzZrBp0yb27NnDZ599BkC2bNm46qqrOHDgAHXr1mXdunUArF27lsaNG/PTTz9RpUoVEhMTueaaa3jyySfJly8fZcuWBfjLNkmSpPTMCCVJktKNI4EJ/l/g2bZtG0888QTR0dGUKVOGqKgowuEwJUqU4J///CeLFi1i+fLlXHzxxbz77rtkz56dvn37sn37dt555x1efPFFVq9ezYIFCyJnS2XOnJkbb7yR3bt306RJE15++WWmTJnCDz/8QIECBXj77bfZvHkzl19+OcWLF2fr1q3kypWLMmXKkCNHDvLlyxfY75EkSVJQvBxPkiSlG38+s6hHjx589913nH/++bz88su88847wOF7QB06dAiAdu3asWHDBubNmwdAnjx52L59OyNHjqRkyZJ0796dlStXsn//fsLhMAULFmTkyJFcfPHFfP/991x++eXUqFGD888/n88++4x8+fLRvHlzLrnkEm666SaKFy8OQP78+QHo3LkzHTt2TMnfEkmSpFTDCCVJktKNxYsXExsbC8DMmTN5+umnyZIlCx07duS1114DDp/FlDVrVr7//nteeOEFvv/+e+bNm8fBgwcpWbIkRYsWZenSpRw8eJDNmzeTlJRErVq1iIuLY8SIEXTo0IEqVarw+++/U65cOf7v//6PV155hQEDBlCvXj1GjBhB7ty5j7nvyCfySZIkZURGKEmSlG7kzJmTWbNmMWfOHPr27cvHH39McnIyrVu35rvvvuOTTz4hISGB8uXLc+2115KUlMRNN93E999/z/Tp0xk6dCjr16/n119/JTExkeuuu47p06czbNgwNmzYQJEiRciSJQv33nsvnTt3pm7dukRFRVGmTBkmT55MhQoVWLhw4XH3HflEPkmSpIzIe0JJkqR0o1y5clx00UVMnTqVuLg4Nm7cyEcffcTNN9/M1Vdfzb///W/q1KnDU089RYMGDShWrBjvvfce7du35+abb46cqZQtWzZKlizJjTfeyLPPPstXX31FzZo1KVGiBFFRUVx//fVcf/31R713kSJFuOeeeyhWrFgQP7okSVKqZ4SSJEnpRlRUFLVq1eLDDz/kwIEDzJs3jyuuuAKAZs2a8dtvvzF58mTOO+88Vq1axb333svYsWNJTEwEoFatWvTq1YtcuXLx2WefMXv2bAoUKMDkyZO5+uqrT/jeN9xwAzfccMM5/xklSZLSqqjwkY95kSRJSgf27dtHYmIiefLkOer7K1eupEKFCowZM4Yvv/yScePGRW5QXq5c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uU74ukyZDv+WJSUlndb7pnWZM2dmzJgxlCxZkoSEhKDnZGg9evTg/fffJ1OmTNx1110sW7aM11577bj3YJMkSZIkKUinfGPyHTt2RC6B27JlS+TeTyfy7rvvMmrUKM4777xTX/g3jsSgqlWr8sorr5z06458mp9OTVJSEvfccw9r1qzh1VdfDXpOhhYfH0+hQoWIjo6mYsWKQc+RJEmSJOmETjlCvfnmm+zfv/+UXrN7927effddWrduHflevnz5Il9v2rSJChUqHPf1mzZtOu5jBQoUiLyH9745t5KSkmjbti0TJ07k1Vdf9VPyAlajRg1q1KgR9AxJkiRJkk7KKUeoI5fWFStWjCFDhvzt87t06cKaNWsYP378URHqz/erWbBgATVr1jzuMY7c9+lYrrjiCmbPns3KlStZv349RYsWPZkf45yIiooK7L3PNQOUJEmSJEk6E6cUoX799VdmzZoFQNOmTWnRosXfvubrr79m2LBhTJ8+nT/++CNyGdxVV11F3rx52bFjB6+88gqPPPLIMV+/YcMGPvnkk+Me/5ZbbmHkyJGEw2GGDRtG3759T+VHOqty5MgR+frAgQOB7TjbDFCSJEmSJOlMndKNycePH084HAagWbNmJ/WaI89LTk4+6p5NOXLkoE2bNgB88803DBs27C+vTU5O5r777jvh5X833ngj1atXB2DgwIG8+eabJ9yzZMkSpkyZclLbT1WBAgUi98j65Zdfzsl7pDQDlCRJkiRJOhtO6UyoCRMmAFC4cGFq1ap1Uq+55pprKFasGOvWrWPChAl07do18lh8fDxvvfUW69ev59FHH2XBggXcddddFCpUiBUrVjBs2DBmz55N9erVmTdvHnDsS95ee+01qlevztatW2nevDmvvPIKzZs3p3z58mTOnJmNGzeycOFCpkyZwtdff03nzp1p1KjRqfzoJyVLlixcffXVzJo1i5deeokrrriCyy+/nKxZswKQP39+8ufPf0rH/Oqrr1ixYkXk15s3b458vWLFCsaOHXvU89u2bXva+/+XAUqSJEmSJJ0tJx2hZs2aFTm7p0mTJmTKdHInUWXKlIkmTZowatQovv/+exYsWEC1atWAw1Hm448/pl69emzatIkJEyZEQtcRbdu2pVatWpEI9edL3o4oW7Ysc+bMoWnTpixdupQpU6ac8GynPHnynNT209G9e3caNWrEli1baNmy5VGPxcXFER8ff0rHe/HFFxk3btwxH5s1a1bk8sgjzlaEMkBJkiRJkqSz6aQvxztyQ3I4fD+oU/Hn5//5OABVq1Zl2bJldO7cmfLly5M9e3YKFizI9ddfz2uvvcbLL7/Mzp07I8/PmzfvMd+jQoUKLFq0iNdee42mTZtSqlQpcubMSbZs2ShWrBi1a9cmOjqaBQsWEBsbe0r7T8XNN9/MZ599RuPGjSlevHjkLKi0xAAlSZIkSZLOtqj9+/eHc+TIwYQJE2jVqlXQe46pffv2jBkzhhIlSrB69eqg56RrBihJkiRJknQunNKNyYOwb98+Jk+eDECNGjUCXpO+GaAkSZIkSdK5EniE+uWXXyKfuPe/kpKSeOCBByI347777rtTclqGYoCSJEmSJEnn0il9Ot650LNnT+bNm0eLFi34v//7PwoXLsy+fftYvHgxL7zwAt9++y0AdevW5eabbw54bfpkgJIkSZIkSeda4BEKYPny5cTFxR338Zo1a/L6668TFRWVgqsyBgOUJEmSJElKCYFHqO7du1OhQgWmTZvGqlWr2LRpE4mJiRQoUICrrrqK5s2b06JFCzJlCvzKwXTHACVJkiRJklJKmvh0PJ19BihJkiRJkpSSPL0oAzJASZIkSZKklGaEymAMUJIkSZIkKQhGqAzEACVJkiRJkoJihMogDFCSJEmSJClIRqgMwAAlSZIkSZKCZoRK5wxQkiRJkiQpNYg6cOBAOEeOHBQqVIjzzz8/6D06CZkzZ2bMmDH861//OuHzDFCSJEmSJCm1yJItWzbeeecd5s2bF/QWnaTnnnuOKVOmnDBCGaAkSZIkSVJqEhUOh8NBj9CpKV++PLfddhv9+/c/5uMGKEmSJEmSlNp4T6h0xgAlSZIkSZJSIyNUOmKAkiRJkiRJqZURKp0wQEmSJEmSpNTs/wOAYHXujjfF3QAAAABJRU5ErkJggg==",
"text/plain": [
"<Figure size 1000x500 with 1 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"\u001b[33muser_proxy\u001b[0m (to chatbot):\n",
"\n",
"\u001b[32m***** Response from calling function \"python\" *****\u001b[0m\n",
"(0.27999999999999997, 0.72, 0.39, 0.61)\n",
"\u001b[32m***************************************************\u001b[0m\n",
"\n",
"--------------------------------------------------------------------------------\n",
"\u001b[35m******************************Start compressing the following content:******************************\u001b[0m\n",
"To be compressed:\n",
"##FUNCTION_CALL## \n",
"Name: python\n",
"Args: {\n",
" \"cell\": \"import matplotlib.pyplot as plt\\n\n",
"plt.figure(figsize=(10, 5))\\n\n",
"\n",
"# Representing two agents\\n\n",
"plt.text(0.2, 0.4, 'Agent 1', ha='center', va='center', size=20, bbox=dict(boxstyle=\\\"rarrow\\\", fc=\\\"w\\\"))\\n\n",
"plt.text(0.8, 0.6, 'Agent 2', ha='center', va='center', size=20, bbox=dict(boxstyle=\\\"larrow\\\", fc=\\\"w\\\"))\\n\n",
"\n",
"# Dialog lines\\n\n",
"plt.plot([0.3, 0.7], [0.4, 0.6], 'k-')\\n\n",
"\n",
"# Dialog\\n\n",
"plt.text(0.5, 0.5, '\\\"Hello!\\\"', ha='center', va='center', rotation=20)\\n\n",
"plt.text(0.5, 0.5, '\\\"Hi!\\\"', ha='center', va='center', rotation=-15)\\n\n",
"\n",
"# Hide axes\\n\n",
"plt.axis('off')\"\n",
"}\n",
"##FUNCTION_RETURN## (from function \"python\"): \n",
"(0.27999999999999997, 0.72, 0.39, 0.61)\n",
"\n",
"\u001b[35m******************************Content after compressing: (type=<class 'str'>)******************************\u001b[0m\n",
"##FUNCTION_CALL##\n",
"Name: python\n",
"Args: A code cell creating a plot representing a dialogue between two agents, Agent 1 and Agent 2, using matplotlib. Dialog lines and text are included with axes hidden.\n",
"\n",
"##FUNCTION_RETURN##\n",
"The python function results in a plot area of (0.28, 0.72, 0.39, 0.61). \u001b[35m\n",
"********************************************************************************\u001b[0m\n",
"\u001b[35mToken Count (of msgs after first prompt): Before compression: 312 After: 102 | Total prompt token count after compression: 231\u001b[0m\n",
"--------------------------------------------------------------------------------\n",
"\u001b[33mchatbot\u001b[0m (to user_proxy):\n",
"\n",
"TERMINATE\n",
"\n",
"--------------------------------------------------------------------------------\n"
]
}
],
"source": [
"llm_config = {\n",
" \"functions\": [\n",
" {\n",
" \"name\": \"python\",\n",
" \"description\": \"run cell in ipython and return the execution result.\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"cell\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"Valid Python cell to execute.\",\n",
" }\n",
" },\n",
" \"required\": [\"cell\"],\n",
" },\n",
" },\n",
" {\n",
" \"name\": \"sh\",\n",
" \"description\": \"run a shell script and return the execution result.\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"script\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"Valid shell script to execute.\",\n",
" }\n",
" },\n",
" \"required\": [\"script\"],\n",
" },\n",
" },\n",
" ],\n",
" \"config_list\": config_list,\n",
" \"request_timeout\": 120,\n",
"}\n",
"chatbot = autogen.AssistantAgent(\n",
" name=\"chatbot\",\n",
" system_message=\"For coding tasks, only use the functions you have been provided with. Reply TERMINATE when the task is done.\",\n",
" llm_config=llm_config,\n",
" compress_config={\n",
" \"mode\": \"COMPRESS\",\n",
" \"trigger_count\": 300, # set this to a large number for less frequent compression\n",
" }\n",
"\n",
")\n",
"\n",
"# create a UserProxyAgent instance named \"user_proxy\"\n",
"user_proxy = autogen.UserProxyAgent(\n",
" name=\"user_proxy\",\n",
" is_termination_msg=lambda x: x.get(\"content\", \"\") and x.get(\"content\", \"\").rstrip().endswith(\"TERMINATE\"),\n",
" human_input_mode=\"NEVER\",\n",
" max_consecutive_auto_reply=10,\n",
" code_execution_config={\"work_dir\": \"coding\"},\n",
")\n",
"\n",
"# define functions according to the function desription\n",
"from IPython import get_ipython\n",
"\n",
"def exec_python(cell):\n",
" ipython = get_ipython()\n",
" result = ipython.run_cell(cell)\n",
" log = str(result.result)\n",
" if result.error_before_exec is not None:\n",
" log += f\"\\n{result.error_before_exec}\"\n",
" if result.error_in_exec is not None:\n",
" log += f\"\\n{result.error_in_exec}\"\n",
" return log\n",
"\n",
"def exec_sh(script):\n",
" return user_proxy.execute_code_blocks([(\"sh\", script)])\n",
"\n",
"# register the functions\n",
"user_proxy.register_function(\n",
" function_map={\n",
" \"python\": exec_python,\n",
" \"sh\": exec_sh,\n",
" }\n",
")\n",
"\n",
"# start the conversation\n",
"user_proxy.initiate_chat(\n",
" chatbot,\n",
" message=\"Draw two agents chatting with each other with an example dialog. Don't add plt.show().\",\n",
")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "msft",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.17"
},
"orig_nbformat": 4
},
"nbformat": 4,
"nbformat_minor": 2
}