From 3781f23ebaa1981b4b84661d5ef54f4c95f6875b Mon Sep 17 00:00:00 2001 From: Xiaoyun Zhang Date: Wed, 21 Aug 2024 06:52:31 -0700 Subject: [PATCH 01/16] Update CONTRIBUTORS.md (#3393) --- CONTRIBUTORS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index a7f45ed11dea..a076d1f2b06f 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -23,6 +23,7 @@ | Ricky Loynd | [rickyloyn-microsoft](https://github.com/rickyloynd-microsoft) | Microsoft Research | Learning | | | | Eric Zhu | [ekzhu](https://github.com/ekzhu) | Microsoft Research | Infra | | | | Jack Gerrits | [jackgerrits](https://github.com/jackgerrits) | Microsoft Research | Infra | | | +| David Luong | [David Luong](https://github.com/DavidLuong98) | Microsoft | AutoGen.Net | | | ## I would like to join this list. How can I help the project? From aae881b2ac861d1012183f0ad0fe850ce16f7cd5 Mon Sep 17 00:00:00 2001 From: Ricky Loynd Date: Wed, 21 Aug 2024 06:52:47 -0700 Subject: [PATCH 02/16] Update CONTRIBUTORS.md (#3391) Modify the display text of my github handle. --- CONTRIBUTORS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index a076d1f2b06f..4a7a3a7e4fab 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -20,7 +20,7 @@ | Beibin Li | [BeibinLi](https://github.com/BeibinLi) | Microsoft Research | alt-models | Yes | | | Gagan Bansal | [gagb](https://github.com/gagb) | Microsoft Research | Complex Tasks | | | | Adam Fourney | [afourney](https://github.com/afourney) | Microsoft Research | Complex Tasks | | | -| Ricky Loynd | [rickyloyn-microsoft](https://github.com/rickyloynd-microsoft) | Microsoft Research | Learning | | | +| Ricky Loynd | [rickyloynd-microsoft](https://github.com/rickyloynd-microsoft) | Microsoft Research | Teachability | | | | Eric Zhu | [ekzhu](https://github.com/ekzhu) | Microsoft Research | Infra | | | | Jack Gerrits | [jackgerrits](https://github.com/jackgerrits) | Microsoft Research | Infra | | | | David Luong | [David Luong](https://github.com/DavidLuong98) | Microsoft | AutoGen.Net | | | From 740df2826032a55903da5cd300754194532979f6 Mon Sep 17 00:00:00 2001 From: Andy Zhou Date: Wed, 21 Aug 2024 10:58:53 -0700 Subject: [PATCH 03/16] Add Language Agent Tree Search (LATS) notebook (#3376) * Add Language Agent Tree Search (LATS) notebook * removed outputs --------- Co-authored-by: Andy Zhou Co-authored-by: Shaokun Zhang --- notebook/lats_search.ipynb | 1059 ++++++++++++++++++++++++++++++++++++ 1 file changed, 1059 insertions(+) create mode 100644 notebook/lats_search.ipynb diff --git a/notebook/lats_search.ipynb b/notebook/lats_search.ipynb new file mode 100644 index 000000000000..01b4449890ed --- /dev/null +++ b/notebook/lats_search.ipynb @@ -0,0 +1,1059 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "211913e6", + "metadata": {}, + "source": [ + "# Language Agent Tree Search\n", + "\n", + "[Language Agent Tree Search](https://arxiv.org/abs/2310.04406) (LATS), by Zhou, et. al, is a general LLM agent search algorithm that combines reflection/evaluation and search (specifically Monte-Carlo tree search) to achieve stronger overall task performance by leveraging inference-time compute.\n", + "\n", + "It has four main phases consisting of six steps:\n", + "\n", + "1. Select: pick the best next state to progress from, based on its aggregate value. \n", + "2. Expand and simulate: sample n potential actions to take and execute them in parallel.\n", + "3. Reflect + Evaluate: observe the outcomes of these actions and score the decisions based on reflection (and possibly external feedback if available)\n", + "4. Backpropagate: update the scores of the root trajectories based on the outcomes.\n", + "\n", + "![lats](https://i.postimg.cc/NjQScLTv/image.png)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "da705b29", + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import logging\n", + "import os\n", + "import uuid\n", + "from typing import Any, Dict, List\n", + "\n", + "from autogen import AssistantAgent, ConversableAgent, GroupChat, UserProxyAgent, config_list_from_json" + ] + }, + { + "cell_type": "markdown", + "id": "293fd23b", + "metadata": {}, + "source": [ + "# Configure logging\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a02f8a2c", + "metadata": {}, + "outputs": [], + "source": [ + "logging.basicConfig(level=logging.INFO)" + ] + }, + { + "cell_type": "markdown", + "id": "1d5ca06b", + "metadata": {}, + "source": [ + "# Set environment variables\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1566c7df", + "metadata": {}, + "outputs": [], + "source": [ + "os.environ[\"AUTOGEN_USE_DOCKER\"] = \"0\" # Disable Docker usage globally for Autogen\n", + "os.environ[\"OPENAI_API_KEY\"] = \"YOUR_API_KEY\"" + ] + }, + { + "cell_type": "markdown", + "id": "585654ac", + "metadata": {}, + "source": [ + "## Prerequisites\n", + "\n", + "Install `autogen` (for the LLM framework and agents)\n", + "\n", + "Required packages: autogen\n", + "\n", + "Please ensure these packages are installed before running this script" + ] + }, + { + "cell_type": "markdown", + "id": "586bcf0f", + "metadata": {}, + "source": [ + "# Directly create the config_list with the API key" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9eaf711f", + "metadata": {}, + "outputs": [], + "source": [ + "config_list = [{\"model\": \"gpt-4o-mini\", \"api_key\": \"YOUR_API_KEY\"}]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "79701018", + "metadata": {}, + "outputs": [], + "source": [ + "if not config_list:\n", + " raise ValueError(\"Failed to create configuration. Please check the API key.\")" + ] + }, + { + "cell_type": "markdown", + "id": "9041e0a3", + "metadata": {}, + "source": [ + "### Reflection Class\n", + "\n", + "The reflection chain will score agent outputs based on the decision and the tool responses." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ce0288e9", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "from pydantic import BaseModel, Field\n", + "\n", + "\n", + "class Reflection(BaseModel):\n", + " reflections: str = Field(\n", + " description=\"The critique and reflections on the sufficiency, superfluency,\"\n", + " \" and general quality of the response\"\n", + " )\n", + " score: int = Field(\n", + " description=\"Score from 0-10 on the quality of the candidate response.\",\n", + " gte=0,\n", + " lte=10,\n", + " )\n", + " found_solution: bool = Field(description=\"Whether the response has fully solved the question or task.\")\n", + "\n", + " def as_message(self):\n", + " return {\"role\": \"human\", \"content\": f\"Reasoning: {self.reflections}\\nScore: {self.score}\"}\n", + "\n", + " @property\n", + " def normalized_score(self) -> float:\n", + " return self.score / 10.0" + ] + }, + { + "cell_type": "markdown", + "id": "1f6d3476", + "metadata": {}, + "source": [ + "## Tree State\n", + "\n", + "LATS is based on a (greedy) Monte-Carlo tree search. For each search steps, it picks the node with the highest \"upper confidence bound\", which is a metric that balances exploitation (highest average reward) and exploration (lowest visits). Starting from that node, it generates N (5 in this case) new candidate actions to take, and adds them to the tree. It stops searching either when it has generated a valid solution OR when it has reached the maximum number of rollouts (search tree depth)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b6d0d7a6", + "metadata": {}, + "outputs": [], + "source": [ + "import math\n", + "import os\n", + "from collections import deque\n", + "from typing import Optional" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "305a29d6", + "metadata": {}, + "outputs": [], + "source": [ + "class Node:\n", + " def __init__(\n", + " self,\n", + " messages: List[Dict[str, str]],\n", + " reflection: Optional[Reflection] = None,\n", + " parent: Optional[\"Node\"] = None,\n", + " ):\n", + " self.messages = messages\n", + " self.parent = parent\n", + " self.children: List[\"Node\"] = []\n", + " self.value = 0.0\n", + " self.visits = 0\n", + " self.reflection = reflection\n", + " self.depth = parent.depth + 1 if parent is not None else 1\n", + " self._is_solved = reflection.found_solution if reflection else False\n", + " if self._is_solved:\n", + " self._mark_tree_as_solved()\n", + " if reflection:\n", + " self.backpropagate(reflection.normalized_score)\n", + "\n", + " def __repr__(self) -> str:\n", + " return (\n", + " f\"\"\n", + " )\n", + "\n", + " @property\n", + " def is_solved(self) -> bool:\n", + " \"\"\"If any solutions exist, we can end the search.\"\"\"\n", + " return self._is_solved\n", + "\n", + " @property\n", + " def is_terminal(self):\n", + " return not self.children\n", + "\n", + " @property\n", + " def best_child(self):\n", + " \"\"\"Select the child with the highest UCT to search next.\"\"\"\n", + " if not self.children:\n", + " return None\n", + " all_nodes = self._get_all_children()\n", + " return max(all_nodes, key=lambda child: child.upper_confidence_bound())\n", + "\n", + " @property\n", + " def best_child_score(self):\n", + " \"\"\"Return the child with the highest value.\"\"\"\n", + " if not self.children:\n", + " return None\n", + " return max(self.children, key=lambda child: int(child.is_solved) * child.value)\n", + "\n", + " @property\n", + " def height(self) -> int:\n", + " \"\"\"Check for how far we've rolled out the tree.\"\"\"\n", + " if self.children:\n", + " return 1 + max([child.height for child in self.children])\n", + " return 1\n", + "\n", + " def upper_confidence_bound(self, exploration_weight=1.0):\n", + " \"\"\"Return the UCT score. This helps balance exploration vs. exploitation of a branch.\"\"\"\n", + " if self.parent is None:\n", + " raise ValueError(\"Cannot obtain UCT from root node\")\n", + " if self.visits == 0:\n", + " return self.value\n", + " # Encourages exploitation of high-value trajectories\n", + " average_reward = self.value / self.visits\n", + " exploration_term = math.sqrt(math.log(self.parent.visits) / self.visits)\n", + " return average_reward + exploration_weight * exploration_term\n", + "\n", + " def backpropagate(self, reward: float):\n", + " \"\"\"Update the score of this node and its parents.\"\"\"\n", + " node = self\n", + " while node:\n", + " node.visits += 1\n", + " node.value = (node.value * (node.visits - 1) + reward) / node.visits\n", + " node = node.parent\n", + "\n", + " def get_messages(self, include_reflections: bool = True):\n", + " if include_reflections and self.reflection:\n", + " return self.messages + [self.reflection.as_message()]\n", + " return self.messages\n", + "\n", + " def get_trajectory(self, include_reflections: bool = True) -> List[Dict[str, str]]:\n", + " \"\"\"Get messages representing this search branch.\"\"\"\n", + " messages = []\n", + " node = self\n", + " while node:\n", + " messages.extend(node.get_messages(include_reflections=include_reflections)[::-1])\n", + " node = node.parent\n", + " # Reverse the final back-tracked trajectory to return in the correct order\n", + " return messages[::-1] # root solution, reflection, child 1, ...\n", + "\n", + " def _get_all_children(self):\n", + " all_nodes = []\n", + " nodes = deque()\n", + " nodes.append(self)\n", + " while nodes:\n", + " node = nodes.popleft()\n", + " all_nodes.extend(node.children)\n", + " for n in node.children:\n", + " nodes.append(n)\n", + " return all_nodes\n", + "\n", + " def get_best_solution(self):\n", + " \"\"\"Return the best solution from within the current sub-tree.\"\"\"\n", + " all_nodes = [self] + self._get_all_children()\n", + " best_node = max(\n", + " all_nodes,\n", + " # We filter out all non-terminal, non-solution trajectories\n", + " key=lambda node: int(node.is_terminal and node.is_solved) * node.value,\n", + " )\n", + " return best_node\n", + "\n", + " def _mark_tree_as_solved(self):\n", + " parent = self.parent\n", + " while parent:\n", + " parent._is_solved = True\n", + " parent = parent.parent" + ] + }, + { + "cell_type": "markdown", + "id": "98b719d9", + "metadata": {}, + "source": [ + "The main component is the tree, represented by the root node." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "586d953a", + "metadata": {}, + "outputs": [], + "source": [ + "from typing_extensions import TypedDict\n", + "\n", + "\n", + "class TreeState(TypedDict):\n", + " # The full tree\n", + " root: Node\n", + " # The original input\n", + " input: str" + ] + }, + { + "cell_type": "markdown", + "id": "3a61a6ee", + "metadata": {}, + "source": [ + "## Define Language Agent\n", + "\n", + "Our agent will have three primary LLM-powered processes:\n", + "\n", + "1. Reflect: score the action based on the tool response.\n", + "2. Initial response: to create the root node and start the search.\n", + "3. Expand: generate 5 candidate \"next steps\" from the best spot in the current tree\n", + "\n", + "For more \"Grounded\" tool applications (such as code synthesis), you could integrate code execution into the reflection/reward step. This type of external feedback is very useful." + ] + }, + { + "cell_type": "markdown", + "id": "a9e6c27f", + "metadata": {}, + "source": [ + "#### Tools\n", + "For our example, we will give the language agent a search engine." + ] + }, + { + "cell_type": "markdown", + "id": "ffb10a00", + "metadata": {}, + "source": [ + "Define the UserProxyAgent with web search / tool-use capability\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e467f73e", + "metadata": {}, + "outputs": [], + "source": [ + "user_proxy = UserProxyAgent(\n", + " name=\"user\",\n", + " human_input_mode=\"NEVER\",\n", + " max_consecutive_auto_reply=10,\n", + " code_execution_config={\n", + " \"work_dir\": \"web\",\n", + " \"use_docker\": False,\n", + " },\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "5c2b96b2", + "metadata": {}, + "source": [ + "Create a ConversableAgent without tools\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "212daaef", + "metadata": {}, + "outputs": [], + "source": [ + "assistant_agent = ConversableAgent(\n", + " name=\"assistant_agent\",\n", + " system_message=\"You are an AI assistant capable of helping with various tasks.\",\n", + " human_input_mode=\"NEVER\",\n", + " code_execution_config=False,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "527c1a39", + "metadata": {}, + "source": [ + "### Reflection\n", + "\n", + "Self-reflection allows the agent to boostrap, improving its future responses based on the outcome of previous ones. In agents this is more powerful since it can use external feedback to improve." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3bdd8a23", + "metadata": {}, + "outputs": [], + "source": [ + "reflection_prompt = \"\"\"\n", + "Reflect and grade the assistant response to the user question below.\n", + "User question: {input}\n", + "Assistant response: {candidate}\n", + "\n", + "Provide your reflection in the following format:\n", + "Reflections: [Your detailed critique and reflections]\n", + "Score: [A score from 0-10]\n", + "Found Solution: [true/false]\n", + "\"\"\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7750d32f", + "metadata": {}, + "outputs": [], + "source": [ + "reflection_agent = AssistantAgent(\n", + " name=\"reflection_agent\",\n", + " system_message=\"You are an AI assistant that reflects on and grades responses.\",\n", + " llm_config={\n", + " \"config_list\": config_list,\n", + " \"temperature\": 0.2,\n", + " },\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "23f26bf0", + "metadata": {}, + "outputs": [], + "source": [ + "def reflection_chain(inputs: Dict[str, Any]) -> Reflection:\n", + " try:\n", + " candidate_content = \"\"\n", + " if \"candidate\" in inputs:\n", + " candidate = inputs[\"candidate\"]\n", + " if isinstance(candidate, list):\n", + " candidate_content = (\n", + " candidate[-1][\"content\"]\n", + " if isinstance(candidate[-1], dict) and \"content\" in candidate[-1]\n", + " else str(candidate[-1])\n", + " )\n", + " elif isinstance(candidate, dict):\n", + " candidate_content = candidate.get(\"content\", str(candidate))\n", + " elif isinstance(candidate, str):\n", + " candidate_content = candidate\n", + " else:\n", + " candidate_content = str(candidate)\n", + "\n", + " formatted_prompt = [\n", + " {\"role\": \"system\", \"content\": \"You are an AI assistant that reflects on and grades responses.\"},\n", + " {\n", + " \"role\": \"user\",\n", + " \"content\": reflection_prompt.format(input=inputs.get(\"input\", \"\"), candidate=candidate_content),\n", + " },\n", + " ]\n", + " response = reflection_agent.generate_reply(formatted_prompt)\n", + "\n", + " # Parse the response\n", + " response_str = str(response)\n", + " lines = response_str.split(\"\\n\")\n", + " reflections = next((line.split(\": \", 1)[1] for line in lines if line.startswith(\"Reflections:\")), \"\")\n", + " score_str = next((line.split(\": \", 1)[1] for line in lines if line.startswith(\"Score:\")), \"0\")\n", + " try:\n", + " if \"/\" in score_str:\n", + " numerator, denominator = map(int, score_str.split(\"/\"))\n", + " score = int((numerator / denominator) * 10)\n", + " else:\n", + " score = int(score_str)\n", + " except ValueError:\n", + " logging.warning(f\"Invalid score value: {score_str}. Defaulting to 0.\")\n", + " score = 0\n", + "\n", + " found_solution = next(\n", + " (line.split(\": \", 1)[1].lower() == \"true\" for line in lines if line.startswith(\"Found Solution:\")), False\n", + " )\n", + "\n", + " if not reflections:\n", + " logging.warning(\"No reflections found in the response. Using default values.\")\n", + " reflections = \"No reflections provided.\"\n", + "\n", + " return Reflection(reflections=reflections, score=score, found_solution=found_solution)\n", + " except Exception as e:\n", + " logging.error(f\"Error in reflection_chain: {str(e)}\", exc_info=True)\n", + " return Reflection(reflections=f\"Error in reflection: {str(e)}\", score=0, found_solution=False)" + ] + }, + { + "cell_type": "markdown", + "id": "fc4b9911", + "metadata": {}, + "source": [ + "### Initial Response\n", + "\n", + "We start with a single root node, generated by this first step. It responds to the user input either with a tool invocation or a response." + ] + }, + { + "cell_type": "markdown", + "id": "60675131", + "metadata": {}, + "source": [ + "# Create Autogen agents\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "fd743ab5", + "metadata": {}, + "outputs": [], + "source": [ + "assistant = AssistantAgent(name=\"assistant\", llm_config={\"config_list\": config_list}, code_execution_config=False)\n", + "user = UserProxyAgent(\n", + " name=\"user\",\n", + " human_input_mode=\"NEVER\",\n", + " max_consecutive_auto_reply=10,\n", + " code_execution_config={\"work_dir\": \"web\", \"use_docker\": False},\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "1f93b734", + "metadata": {}, + "source": [ + "# Define a function to create the initial prompt\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b7e00575", + "metadata": {}, + "outputs": [], + "source": [ + "def create_initial_prompt(input_text):\n", + " return [\n", + " {\"role\": \"system\", \"content\": \"You are an AI assistant.\"},\n", + " {\"role\": \"user\", \"content\": input_text},\n", + " ]" + ] + }, + { + "cell_type": "markdown", + "id": "b8442317", + "metadata": {}, + "source": [ + "# Function to generate initial response\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b7afcd1b", + "metadata": {}, + "outputs": [], + "source": [ + "def generate_initial_response(state: TreeState) -> TreeState:\n", + " chat_messages = create_initial_prompt(state[\"input\"])\n", + " try:\n", + " # Ensure chat_messages is a list of dictionaries\n", + " if not isinstance(chat_messages, list):\n", + " chat_messages = [{\"role\": \"user\", \"content\": chat_messages}]\n", + "\n", + " logging.info(f\"Generating initial response for input: {state['input']}\")\n", + " logging.debug(f\"Chat messages: {chat_messages}\")\n", + "\n", + " response = assistant.generate_reply(chat_messages)\n", + " logging.debug(f\"Raw response from assistant: {response}\")\n", + "\n", + " # Ensure response is properly formatted as a string\n", + " if isinstance(response, str):\n", + " content = response\n", + " elif isinstance(response, dict) and \"content\" in response:\n", + " content = response[\"content\"]\n", + " elif isinstance(response, list) and len(response) > 0:\n", + " content = response[-1].get(\"content\", str(response[-1]))\n", + " else:\n", + " content = str(response)\n", + "\n", + " content = content.strip()\n", + " if not content:\n", + " raise ValueError(\"Generated content is empty after processing\")\n", + "\n", + " logging.debug(f\"Processed content: {content[:100]}...\") # Log first 100 chars\n", + "\n", + " # Generate reflection\n", + " reflection_input = {\"input\": state[\"input\"], \"candidate\": content}\n", + " logging.info(\"Generating reflection on the initial response\")\n", + " reflection = reflection_chain(reflection_input)\n", + " logging.debug(f\"Reflection generated: {reflection}\")\n", + "\n", + " # Create Node with messages as a list containing a single dict\n", + " messages = [{\"role\": \"assistant\", \"content\": content}]\n", + " root = Node(messages=messages, reflection=reflection)\n", + "\n", + " logging.info(\"Initial response and reflection generated successfully\")\n", + " return TreeState(root=root, input=state[\"input\"])\n", + "\n", + " except Exception as e:\n", + " logging.error(f\"Error in generate_initial_response: {str(e)}\", exc_info=True)\n", + " return TreeState(root=None, input=state[\"input\"])" + ] + }, + { + "cell_type": "markdown", + "id": "87ef17ca", + "metadata": {}, + "source": [ + "# Example usage of the generate_initial_response function\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7ab75669", + "metadata": {}, + "outputs": [], + "source": [ + "initial_prompt = \"Why is the sky blue?\"\n", + "initial_state = TreeState(input=initial_prompt, root=None)\n", + "result_state = generate_initial_response(initial_state)\n", + "if result_state[\"root\"] is not None:\n", + " print(result_state[\"root\"].messages[0][\"content\"])\n", + "else:\n", + " print(\"Failed to generate initial response.\")" + ] + }, + { + "cell_type": "markdown", + "id": "e619223f", + "metadata": {}, + "source": [ + "#### Starting Node\n", + "\n", + "We will package up the candidate generation and reflection in a single node of our graph. This is represented by the following function:" + ] + }, + { + "cell_type": "markdown", + "id": "24c052e0", + "metadata": {}, + "source": [ + "\n", + "# Define the function to generate the initial response" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "94c92498", + "metadata": {}, + "outputs": [], + "source": [ + "\n", + "# Define the function to generate the initial response\n", + "\n", + "\n", + "def generate_initial_response(state: TreeState) -> TreeState:\n", + " \"\"\"Generate the initial candidate response using Autogen components.\"\"\"\n", + " assistant = AssistantAgent(name=\"assistant\", llm_config={\"config_list\": config_list}, code_execution_config=False)\n", + "\n", + " # Generate initial response\n", + " initial_message = [\n", + " {\"role\": \"system\", \"content\": \"You are an AI assistant.\"},\n", + " {\"role\": \"user\", \"content\": state[\"input\"]},\n", + " ]\n", + "\n", + " try:\n", + " logging.info(f\"Generating initial response for input: {state['input']}\")\n", + " response = assistant.generate_reply(initial_message)\n", + " logging.debug(f\"Raw response from assistant: {response}\")\n", + "\n", + " # Ensure response is properly formatted as a string\n", + " if isinstance(response, str):\n", + " content = response\n", + " elif isinstance(response, dict):\n", + " content = response.get(\"content\", \"\")\n", + " if not content:\n", + " content = json.dumps(response)\n", + " elif isinstance(response, list):\n", + " content = \" \".join(str(item) for item in response)\n", + " else:\n", + " content = str(response)\n", + "\n", + " # Ensure content is always a string and not empty\n", + " content = content.strip()\n", + " if not content:\n", + " raise ValueError(\"Generated content is empty after processing\")\n", + "\n", + " logging.debug(f\"Final processed content (first 100 chars): {content[:100]}...\")\n", + "\n", + " # Generate reflection\n", + " logging.info(\"Generating reflection on the initial response\")\n", + " reflection_input = {\"input\": state[\"input\"], \"candidate\": content}\n", + " reflection = reflection_chain(reflection_input)\n", + " logging.debug(f\"Reflection generated: {reflection}\")\n", + "\n", + " if not isinstance(reflection, Reflection):\n", + " raise TypeError(f\"Invalid reflection type: {type(reflection)}. Expected Reflection, got {type(reflection)}\")\n", + "\n", + " # Create Node with messages as a list containing a single dict\n", + " messages = [{\"role\": \"assistant\", \"content\": content}]\n", + " logging.debug(f\"Creating Node with messages: {messages}\")\n", + " root = Node(messages=messages, reflection=reflection)\n", + " logging.info(\"Initial response and reflection generated successfully\")\n", + " logging.debug(f\"Created root node: {root}\")\n", + " return TreeState(root=root, input=state[\"input\"])\n", + "\n", + " except Exception as e:\n", + " logging.error(f\"Error in generate_initial_response: {str(e)}\", exc_info=True)\n", + " return TreeState(root=None, input=state[\"input\"])" + ] + }, + { + "cell_type": "markdown", + "id": "c58a4074", + "metadata": {}, + "source": [ + "### Candidate Generation\n", + "The following code prompts the same LLM to generate N additional candidates to check.\n", + "\n", + "This generates N candidate values for a single input to sample actions from the environment" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27a3a1db", + "metadata": {}, + "outputs": [], + "source": [ + "def generate_candidates(messages: list, config: dict):\n", + " n = config.get(\"N\", 5)\n", + " assistant = AssistantAgent(name=\"assistant\", llm_config={\"config_list\": config_list}, code_execution_config=False)\n", + "\n", + " candidates = []\n", + " for _ in range(n):\n", + " try:\n", + " # Use the assistant to generate a response\n", + " last_message = messages[-1][\"content\"] if messages and isinstance(messages[-1], dict) else str(messages[-1])\n", + " response = assistant.generate_reply([{\"role\": \"user\", \"content\": last_message}])\n", + " if isinstance(response, str):\n", + " candidates.append(response)\n", + " elif isinstance(response, dict) and \"content\" in response:\n", + " candidates.append(response[\"content\"])\n", + " elif (\n", + " isinstance(response, list) and response and isinstance(response[-1], dict) and \"content\" in response[-1]\n", + " ):\n", + " candidates.append(response[-1][\"content\"])\n", + " else:\n", + " candidates.append(str(response))\n", + " except Exception as e:\n", + " logging.error(f\"Error generating candidate: {str(e)}\")\n", + " candidates.append(\"Failed to generate candidate.\")\n", + "\n", + " if not candidates:\n", + " logging.warning(\"No candidates were generated.\")\n", + "\n", + " return candidates\n", + "\n", + "\n", + "expansion_chain = generate_candidates" + ] + }, + { + "cell_type": "markdown", + "id": "a47c8161", + "metadata": {}, + "source": [ + "#### Candidate generation node\n", + "\n", + "We will package the candidate generation and reflection steps in the following \"expand\" node.\n", + "We do all the operations as a batch process to speed up execution." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "175afca7", + "metadata": {}, + "outputs": [], + "source": [ + "def expand(state: TreeState, config: Dict[str, Any]) -> dict:\n", + " root = state[\"root\"]\n", + " best_candidate: Node = root.best_child if root.children else root\n", + " messages = best_candidate.get_trajectory()\n", + "\n", + " # Generate N candidates using Autogen's generate_candidates function\n", + " new_candidates = generate_candidates(messages, config)\n", + "\n", + " # Reflect on each candidate using Autogen's AssistantAgent\n", + " reflections = []\n", + " for candidate in new_candidates:\n", + " reflection = reflection_chain({\"input\": state[\"input\"], \"candidate\": candidate})\n", + " reflections.append(reflection)\n", + "\n", + " # Grow tree\n", + " child_nodes = [\n", + " Node([{\"role\": \"assistant\", \"content\": candidate}], parent=best_candidate, reflection=reflection)\n", + " for candidate, reflection in zip(new_candidates, reflections)\n", + " ]\n", + " best_candidate.children.extend(child_nodes)\n", + "\n", + " # We have already extended the tree directly, so we just return the state\n", + " return state" + ] + }, + { + "cell_type": "markdown", + "id": "717b7b93", + "metadata": {}, + "source": [ + "## Create Tree\n", + "\n", + "With those two nodes defined, we are ready to define the tree. After each agent step, we have the option of finishing." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e309ea9f", + "metadata": {}, + "outputs": [], + "source": [ + "from typing import Any, Dict, Literal\n", + "\n", + "\n", + "def should_loop(state: Dict[str, Any]) -> Literal[\"expand\", \"end\"]:\n", + " \"\"\"Determine whether to continue the tree search.\"\"\"\n", + " root = state[\"root\"]\n", + " if root.is_solved:\n", + " return \"end\"\n", + " if root.height > 5:\n", + " return \"end\"\n", + " return \"expand\"\n", + "\n", + "\n", + "def run_lats(input_query: str, max_iterations: int = 10):\n", + " import logging\n", + "\n", + " logging.basicConfig(level=logging.INFO)\n", + " logger = logging.getLogger(__name__)\n", + "\n", + " try:\n", + "\n", + " state = {\"input\": input_query, \"root\": None}\n", + " try:\n", + " state = generate_initial_response(state)\n", + " if not isinstance(state, dict) or \"root\" not in state or state[\"root\"] is None:\n", + " logger.error(\"Initial response generation failed or returned invalid state\")\n", + " return \"Failed to generate initial response.\"\n", + " logger.info(\"Initial response generated successfully\")\n", + " except Exception as e:\n", + " logger.error(f\"Error generating initial response: {str(e)}\", exc_info=True)\n", + " return \"Failed to generate initial response due to an unexpected error.\"\n", + "\n", + " for iteration in range(max_iterations):\n", + " action = should_loop(state)\n", + " if action == \"end\":\n", + " logger.info(f\"Search ended after {iteration + 1} iterations\")\n", + " break\n", + " try:\n", + " state = expand(\n", + " state,\n", + " {\n", + " \"N\": 5,\n", + " \"input_query\": input_query,\n", + " },\n", + " )\n", + " logger.info(f\"Completed iteration {iteration + 1}\")\n", + " except Exception as e:\n", + " logger.error(f\"Error during iteration {iteration + 1}: {str(e)}\", exc_info=True)\n", + " continue\n", + "\n", + " if not isinstance(state, dict) or \"root\" not in state or state[\"root\"] is None:\n", + " return \"No valid solution found due to an error in the search process.\"\n", + "\n", + " solution_node = state[\"root\"].get_best_solution()\n", + " best_trajectory = solution_node.get_trajectory(include_reflections=False)\n", + " if not best_trajectory:\n", + " return \"No solution found in the search process.\"\n", + "\n", + " result = (\n", + " best_trajectory[-1].get(\"content\") if isinstance(best_trajectory[-1], dict) else str(best_trajectory[-1])\n", + " )\n", + " logger.info(\"LATS search completed successfully\")\n", + " return result\n", + " except Exception as e:\n", + " logger.error(f\"An unexpected error occurred during LATS execution: {str(e)}\", exc_info=True)\n", + " return f\"An unexpected error occurred: {str(e)}\"" + ] + }, + { + "cell_type": "markdown", + "id": "e274e373", + "metadata": {}, + "source": [ + "Example usage:\n", + "\n", + "result = run_lats(\"Write a research report on deep learning.\")\n", + "\n", + "print(result)" + ] + }, + { + "cell_type": "markdown", + "id": "aa719ff2", + "metadata": {}, + "source": [ + "\n", + "# Example usage of the LATS algorithm with Autogen" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "683c0f2c", + "metadata": {}, + "outputs": [], + "source": [ + "import logging\n", + "\n", + "logging.basicConfig(level=logging.INFO, format=\"%(asctime)s - %(levelname)s - %(message)s\")\n", + "logger = logging.getLogger(__name__)\n", + "\n", + "\n", + "def run_lats_example(question):\n", + " try:\n", + " logger.info(f\"Processing question: {question}\")\n", + " result = run_lats(question)\n", + " logger.info(f\"LATS algorithm completed. Result: {result[:100]}...\") # Log first 100 chars of result\n", + " print(f\"Question: {question}\")\n", + " print(f\"Answer: {result}\")\n", + " except Exception as e:\n", + " logger.error(f\"An error occurred while processing the question: {str(e)}\", exc_info=True)\n", + " print(f\"An error occurred: {str(e)}\")\n", + " finally:\n", + " print(\"---\")" + ] + }, + { + "cell_type": "markdown", + "id": "a4ce778e", + "metadata": {}, + "source": [ + "# List of example questions\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "60fa1f07", + "metadata": {}, + "outputs": [], + "source": [ + "questions = [\n", + " \"Explain how epigenetic modifications can influence gene expression across generations and the implications for evolution.\",\n", + " \"Discuss the challenges of grounding ethical theories in moral realism, especially in light of the is-ought problem introduced by Hume.\",\n", + " \"How does the Riemann Hypothesis relate to the distribution of prime numbers, and why is it significant in number theory?\",\n", + " \"Describe the challenges and theoretical underpinnings of unifying general relativity with quantum mechanics, particularly focusing on string theory and loop quantum gravity.\",\n", + "]" + ] + }, + { + "cell_type": "markdown", + "id": "a0fed5fe", + "metadata": {}, + "source": [ + "# Run LATS algorithm for each question\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5d1e5754", + "metadata": {}, + "outputs": [], + "source": [ + "for i, question in enumerate(questions, 1):\n", + " print(f\"\\nExample {i}:\")\n", + " run_lats_example(question)\n", + "\n", + "logger.info(\"All examples processed.\")" + ] + }, + { + "cell_type": "markdown", + "id": "af7254a5", + "metadata": {}, + "source": [ + "## Conclusion\n", + "\n", + "Congrats on implementing LATS! This is a technique that can be reasonably fast and effective at solving complex agent tasks. A few notes that you probably observed above:\n", + "\n", + "1. While LATS is effective, the tree rollout process can require additional inference compute time. If you plan to integrate this into a production application, consider streaming intermediate steps to allow users to see the thought process and access intermediate results. Alternatively, you could use it to generate fine-tuning data to enhance single-shot accuracy and avoid lengthy rollouts. The cost of using LATS has significantly decreased since its initial proposal and is expected to continue decreasing.\n", + "\n", + "2. The effectiveness of the candidate selection process depends on the quality of the rewards generated. In this example, we exclusively use self-reflection as feedback, but if you have access to external feedback sources (such as code test execution), those should be incorporated as suggested above." + ] + }, + { + "cell_type": "markdown", + "id": "be01ff1e", + "metadata": {}, + "source": [ + "# \n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "venv", + "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.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From 7b5c5eee6d67fa18f39b31fa5d185c0827c60c06 Mon Sep 17 00:00:00 2001 From: Xiaoyun Zhang Date: Wed, 21 Aug 2024 13:20:13 -0700 Subject: [PATCH 04/16] [.Net] Release 0.1.0 (#3398) * update version and release note * Update MetaInfo.props * update release note --- .github/workflows/dotnet-build.yml | 2 +- dotnet/eng/MetaInfo.props | 2 +- dotnet/website/release_note/0.1.0.md | 41 ++++++++++++++++++++++++++++ dotnet/website/release_note/toc.yml | 3 ++ 4 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 dotnet/website/release_note/0.1.0.md diff --git a/.github/workflows/dotnet-build.yml b/.github/workflows/dotnet-build.yml index 7eca61861714..6aac54d3818c 100644 --- a/.github/workflows/dotnet-build.yml +++ b/.github/workflows/dotnet-build.yml @@ -51,7 +51,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ ubuntu-latest, macos-latest, windows-latest ] + os: [ ubuntu-latest, macos-latest ] python-version: ["3.11"] runs-on: ${{ matrix.os }} timeout-minutes: 30 diff --git a/dotnet/eng/MetaInfo.props b/dotnet/eng/MetaInfo.props index 72918fabe4f4..006c586faba5 100644 --- a/dotnet/eng/MetaInfo.props +++ b/dotnet/eng/MetaInfo.props @@ -1,7 +1,7 @@ - 0.0.17 + 0.1.0 AutoGen https://microsoft.github.io/autogen-for-net/ https://github.com/microsoft/autogen diff --git a/dotnet/website/release_note/0.1.0.md b/dotnet/website/release_note/0.1.0.md new file mode 100644 index 000000000000..dc844087758c --- /dev/null +++ b/dotnet/website/release_note/0.1.0.md @@ -0,0 +1,41 @@ +# πŸŽ‰ Release Notes: AutoGen.Net 0.1.0 πŸŽ‰ + +## πŸ“¦ New Packages + +1. **Add AutoGen.AzureAIInference Package** + - **Issue**: [.Net][Feature Request] [#3323](https://github.com/microsoft/autogen/issues/3323) + - **Description**: The new `AutoGen.AzureAIInference` package includes the `ChatCompletionClientAgent`. + +## ✨ New Features + +1. **Enable Step-by-Step Execution for Two Agent Chat API** + - **Issue**: [.Net][Feature Request] [#3339](https://github.com/microsoft/autogen/issues/3339) + - **Description**: The `AgentExtension.SendAsync` now returns an `IAsyncEnumerable`, allowing conversations to be driven step by step, similar to how `GroupChatExtension.SendAsync` works. + +2. **Support Python Code Execution in AutoGen.DotnetInteractive** + - **Issue**: [.Net][Feature Request] [#3316](https://github.com/microsoft/autogen/issues/3316) + - **Description**: `dotnet-interactive` now supports Jupyter kernel connection, allowing Python code execution in `AutoGen.DotnetInteractive`. + +3. **Support Prompt Cache in Claude** + - **Issue**: [.Net][Feature Request] [#3359](https://github.com/microsoft/autogen/issues/3359) + - **Description**: Claude now supports prompt caching, which dramatically lowers the bill if the cache is hit. Added the corresponding option in the Claude client. + +## πŸ› Bug Fixes + +1. **GroupChatExtension.SendAsync Doesn’t Terminate Chat When `IOrchestrator` Returns Null as Next Agent** + - **Issue**: [.Net][Bug] [#3306](https://github.com/microsoft/autogen/issues/3306) + - **Description**: Fixed an issue where `GroupChatExtension.SendAsync` would continue until the max_round is reached even when `IOrchestrator` returns null as the next speaker. + +2. **InitializedMessages Are Added Repeatedly in GroupChatExtension.SendAsync Method** + - **Issue**: [.Net][Bug] [#3268](https://github.com/microsoft/autogen/issues/3268) + - **Description**: Fixed an issue where initialized messages from group chat were being added repeatedly in every iteration of the `GroupChatExtension.SendAsync` API. + +3. **Remove `Azure.AI.OpenAI` Dependency from `AutoGen.DotnetInteractive`** + - **Issue**: [.Net][Feature Request] [#3273](https://github.com/microsoft/autogen/issues/3273) + - **Description**: Fixed an issue by removing the `Azure.AI.OpenAI` dependency from `AutoGen.DotnetInteractive`, simplifying the package and reducing dependencies. + +## πŸ“„ Documentation Updates + +1. **Add Function Comparison Page Between Python AutoGen and AutoGen.Net** + - **Issue**: [.Net][Document] [#3184](https://github.com/microsoft/autogen/issues/3184) + - **Description**: Added comparative documentation for features between AutoGen and AutoGen.Net across various functionalities and platform supports. \ No newline at end of file diff --git a/dotnet/website/release_note/toc.yml b/dotnet/website/release_note/toc.yml index f8753cacc890..9c8008e705e1 100644 --- a/dotnet/website/release_note/toc.yml +++ b/dotnet/website/release_note/toc.yml @@ -1,3 +1,6 @@ +- name: 0.1.0 + href: 0.1.0.md + - name: 0.0.17 href: 0.0.17.md From eb160d9d4a165726e98996f0c11a7258c9949305 Mon Sep 17 00:00:00 2001 From: Xiaoyun Zhang Date: Wed, 21 Aug 2024 13:45:15 -0700 Subject: [PATCH 05/16] [.Net] Rename AutoGen.OpenAI to AutoGen.OpenAI.V1 (#3358) * fix build error * rename AutoGen.OpenAI to AutoGen.OpenAI.V1 --- dotnet/AutoGen.sln | 10 +++++----- .../AutoGen.BasicSamples/CodeSnippet/CreateAnAgent.cs | 2 +- .../CodeSnippet/FunctionCallCodeSnippet.cs | 2 +- .../CodeSnippet/GetStartCodeSnippet.cs | 2 +- .../CodeSnippet/MiddlewareAgentCodeSnippet.cs | 2 +- .../CodeSnippet/OpenAICodeSnippet.cs | 4 ++-- .../CodeSnippet/PrintMessageMiddlewareCodeSnippet.cs | 4 ++-- .../CodeSnippet/TypeSafeFunctionCallCodeSnippet.cs | 2 +- .../Example04_Dynamic_GroupChat_Coding_Task.cs | 2 +- .../AutoGen.BasicSamples/Example05_Dalle_And_GPT4V.cs | 4 ++-- .../AutoGen.BasicSamples/Example06_UserProxyAgent.cs | 2 +- .../Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs | 4 ++-- .../Example09_LMStudio_FunctionCall.cs | 2 +- .../Example11_Sequential_GroupChat_Example.cs | 4 ++-- .../Example12_TwoAgent_Fill_Application.cs | 4 ++-- .../Example15_GPT4V_BinaryDataImageMessage.cs | 2 +- .../AutoGen.BasicSamples/Example17_ReActAgent.cs | 4 ++-- .../GettingStart/Agent_Middleware.cs | 4 ++-- .../GettingStart/Chat_With_Agent.cs | 4 ++-- .../GettingStart/Dynamic_Group_Chat.cs | 4 ++-- .../GettingStart/FSM_Group_Chat.cs | 4 ++-- .../GettingStart/Image_Chat_With_Agent.cs | 4 ++-- .../GettingStart/Streaming_Tool_Call.cs | 4 ++-- .../GettingStart/Use_Tools_With_Agent.cs | 4 ++-- dotnet/sample/AutoGen.BasicSamples/LLMConfiguration.cs | 2 +- ...I.Sample.csproj => AutoGen.OpenAI.V1.Sample.csproj} | 0 .../sample/AutoGen.OpenAI.Sample/Connect_To_Ollama.cs | 3 ++- .../Tool_Call_With_Ollama_And_LiteLLM.cs | 3 ++- dotnet/sample/AutoGen.OpenAI.Sample/Use_Json_Mode.cs | 4 ++-- .../Use_Kernel_Functions_With_Other_Agent.cs | 4 ++-- dotnet/src/AutoGen.LMStudio/AutoGen.LMStudio.csproj | 2 +- dotnet/src/AutoGen.LMStudio/LMStudioAgent.cs | 2 +- .../Agent/GPTAgent.cs | 4 ++-- .../Agent/OpenAIChatAgent.cs | 4 ++-- .../AutoGen.OpenAI.V1.csproj} | 0 .../AzureOpenAIConfig.cs | 2 +- .../Extension/FunctionContractExtension.cs | 2 +- .../Extension/MessageExtension.cs | 2 +- .../Extension/OpenAIAgentExtension.cs | 2 +- .../GlobalUsing.cs | 0 .../Middleware/OpenAIChatRequestMessageConnector.cs | 2 +- .../OpenAIConfig.cs | 2 +- .../AutoGen.SemanticKernel.csproj | 1 - dotnet/src/AutoGen/API/LLMConfigAPI.cs | 2 +- dotnet/src/AutoGen/Agent/ConversableAgent.cs | 2 +- dotnet/src/AutoGen/AutoGen.csproj | 2 +- .../OpenAIMessageTests.BasicMessageTest.approved.txt | 0 .../AutoGen.OpenAI.V1.Tests.csproj} | 2 +- .../GlobalUsing.cs | 0 .../MathClassTest.cs | 4 ++-- .../OpenAIChatAgentTest.cs | 4 ++-- .../OpenAIMessageTests.cs | 2 +- .../KernelFunctionMiddlewareTests.cs | 4 ++-- .../FunctionExample.test.cs | 2 +- .../Orchestrator/RolePlayOrchestratorTests.cs | 4 ++-- dotnet/test/AutoGen.Tests/SingleAgentTest.cs | 4 ++-- dotnet/test/AutoGen.Tests/TwoAgentTest.cs | 2 +- .../OpenAIChatCompletionMiddlewareTests.cs | 4 ++-- 58 files changed, 82 insertions(+), 81 deletions(-) rename dotnet/sample/AutoGen.OpenAI.Sample/{AutoGen.OpenAI.Sample.csproj => AutoGen.OpenAI.V1.Sample.csproj} (100%) rename dotnet/src/{AutoGen.OpenAI => AutoGen.OpenAI.V1}/Agent/GPTAgent.cs (98%) rename dotnet/src/{AutoGen.OpenAI => AutoGen.OpenAI.V1}/Agent/OpenAIChatAgent.cs (99%) rename dotnet/src/{AutoGen.OpenAI/AutoGen.OpenAI.csproj => AutoGen.OpenAI.V1/AutoGen.OpenAI.V1.csproj} (100%) rename dotnet/src/{AutoGen.OpenAI => AutoGen.OpenAI.V1}/AzureOpenAIConfig.cs (95%) rename dotnet/src/{AutoGen.OpenAI => AutoGen.OpenAI.V1}/Extension/FunctionContractExtension.cs (98%) rename dotnet/src/{AutoGen.OpenAI => AutoGen.OpenAI.V1}/Extension/MessageExtension.cs (99%) rename dotnet/src/{AutoGen.OpenAI => AutoGen.OpenAI.V1}/Extension/OpenAIAgentExtension.cs (97%) rename dotnet/src/{AutoGen.OpenAI => AutoGen.OpenAI.V1}/GlobalUsing.cs (100%) rename dotnet/src/{AutoGen.OpenAI => AutoGen.OpenAI.V1}/Middleware/OpenAIChatRequestMessageConnector.cs (99%) rename dotnet/src/{AutoGen.OpenAI => AutoGen.OpenAI.V1}/OpenAIConfig.cs (91%) rename dotnet/test/{AutoGen.OpenAI.Tests => AutoGen.OpenAI.V1.Tests}/ApprovalTests/OpenAIMessageTests.BasicMessageTest.approved.txt (100%) rename dotnet/test/{AutoGen.OpenAI.Tests/AutoGen.OpenAI.Tests.csproj => AutoGen.OpenAI.V1.Tests/AutoGen.OpenAI.V1.Tests.csproj} (96%) rename dotnet/test/{AutoGen.OpenAI.Tests => AutoGen.OpenAI.V1.Tests}/GlobalUsing.cs (100%) rename dotnet/test/{AutoGen.OpenAI.Tests => AutoGen.OpenAI.V1.Tests}/MathClassTest.cs (99%) rename dotnet/test/{AutoGen.OpenAI.Tests => AutoGen.OpenAI.V1.Tests}/OpenAIChatAgentTest.cs (99%) rename dotnet/test/{AutoGen.OpenAI.Tests => AutoGen.OpenAI.V1.Tests}/OpenAIMessageTests.cs (99%) diff --git a/dotnet/AutoGen.sln b/dotnet/AutoGen.sln index 0fcaf15ceb2a..db0b2cbb54c6 100644 --- a/dotnet/AutoGen.sln +++ b/dotnet/AutoGen.sln @@ -26,7 +26,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.SemanticKernel", "s EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.Core", "src\AutoGen.Core\AutoGen.Core.csproj", "{D58D43D1-0617-4A3D-9932-C773E6398535}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.OpenAI", "src\AutoGen.OpenAI\AutoGen.OpenAI.csproj", "{63445BB7-DBB9-4AEF-9D6F-98BBE75EE1EC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.OpenAI.V1", "src\AutoGen.OpenAI.V1\AutoGen.OpenAI.V1.csproj", "{63445BB7-DBB9-4AEF-9D6F-98BBE75EE1EC}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.Mistral", "src\AutoGen.Mistral\AutoGen.Mistral.csproj", "{6585D1A4-3D97-4D76-A688-1933B61AEB19}" EndProject @@ -38,7 +38,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.WebAPI.Tests", "tes EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.SemanticKernel.Tests", "test\AutoGen.SemanticKernel.Tests\AutoGen.SemanticKernel.Tests.csproj", "{1DFABC4A-8458-4875-8DCB-59F3802DAC65}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.OpenAI.Tests", "test\AutoGen.OpenAI.Tests\AutoGen.OpenAI.Tests.csproj", "{D36A85F9-C172-487D-8192-6BFE5D05B4A7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.OpenAI.V1.Tests", "test\AutoGen.OpenAI.V1.Tests\AutoGen.OpenAI.V1.Tests.csproj", "{D36A85F9-C172-487D-8192-6BFE5D05B4A7}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.DotnetInteractive.Tests", "test\AutoGen.DotnetInteractive.Tests\AutoGen.DotnetInteractive.Tests.csproj", "{B61388CA-DC73-4B7F-A7B2-7B9A86C9229E}" EndProject @@ -64,15 +64,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.Gemini.Sample", "sa EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.AotCompatibility.Tests", "test\AutoGen.AotCompatibility.Tests\AutoGen.AotCompatibility.Tests.csproj", "{6B82F26D-5040-4453-B21B-C8D1F913CE4C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.OpenAI.Sample", "sample\AutoGen.OpenAI.Sample\AutoGen.OpenAI.Sample.csproj", "{0E635268-351C-4A6B-A28D-593D868C2CA4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.OpenAI.V1.Sample", "sample\AutoGen.OpenAI.Sample\AutoGen.OpenAI.V1.Sample.csproj", "{0E635268-351C-4A6B-A28D-593D868C2CA4}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.WebAPI.Sample", "sample\AutoGen.WebAPI.Sample\AutoGen.WebAPI.Sample.csproj", "{12079C18-A519-403F-BBFD-200A36A0C083}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.AzureAIInference", "src\AutoGen.AzureAIInference\AutoGen.AzureAIInference.csproj", "{5C45981D-1319-4C25-935C-83D411CB28DF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoGen.AzureAIInference.Tests", "test\AutoGen.AzureAIInference.Tests\AutoGen.AzureAIInference.Tests.csproj", "{5970868F-831E-418F-89A9-4EC599563E16}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.AzureAIInference.Tests", "test\AutoGen.AzureAIInference.Tests\AutoGen.AzureAIInference.Tests.csproj", "{5970868F-831E-418F-89A9-4EC599563E16}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoGen.Tests.Share", "test\AutoGen.Test.Share\AutoGen.Tests.Share.csproj", "{143725E2-206C-4D37-93E4-9EDF699826B2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.Tests.Share", "test\AutoGen.Test.Share\AutoGen.Tests.Share.csproj", "{143725E2-206C-4D37-93E4-9EDF699826B2}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/CreateAnAgent.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/CreateAnAgent.cs index a103f4ec2d4d..45be312cbd5e 100644 --- a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/CreateAnAgent.cs +++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/CreateAnAgent.cs @@ -3,7 +3,7 @@ using AutoGen; using AutoGen.Core; -using AutoGen.OpenAI; +using AutoGen.OpenAI.V1; using FluentAssertions; public partial class AssistantCodeSnippet diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/FunctionCallCodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/FunctionCallCodeSnippet.cs index 2b7e25fee0c5..567476ba21cd 100644 --- a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/FunctionCallCodeSnippet.cs +++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/FunctionCallCodeSnippet.cs @@ -3,7 +3,7 @@ using AutoGen; using AutoGen.Core; -using AutoGen.OpenAI; +using AutoGen.OpenAI.V1; using FluentAssertions; public partial class FunctionCallCodeSnippet diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/GetStartCodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/GetStartCodeSnippet.cs index fe97152183a4..c5cdb35f49bc 100644 --- a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/GetStartCodeSnippet.cs +++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/GetStartCodeSnippet.cs @@ -4,7 +4,7 @@ #region snippet_GetStartCodeSnippet using AutoGen; using AutoGen.Core; -using AutoGen.OpenAI; +using AutoGen.OpenAI.V1; #endregion snippet_GetStartCodeSnippet public class GetStartCodeSnippet diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/MiddlewareAgentCodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/MiddlewareAgentCodeSnippet.cs index 1b5a9a903207..9ad252c1ebeb 100644 --- a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/MiddlewareAgentCodeSnippet.cs +++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/MiddlewareAgentCodeSnippet.cs @@ -3,7 +3,7 @@ using System.Text.Json; using AutoGen.Core; -using AutoGen.OpenAI; +using AutoGen.OpenAI.V1; using FluentAssertions; namespace AutoGen.BasicSample.CodeSnippet; diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/OpenAICodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/OpenAICodeSnippet.cs index cf0452212239..b7b5104e9905 100644 --- a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/OpenAICodeSnippet.cs +++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/OpenAICodeSnippet.cs @@ -3,8 +3,8 @@ #region using_statement using AutoGen.Core; -using AutoGen.OpenAI; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1; +using AutoGen.OpenAI.V1.Extension; using Azure.AI.OpenAI; #endregion using_statement using FluentAssertions; diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/PrintMessageMiddlewareCodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/PrintMessageMiddlewareCodeSnippet.cs index bf4f9c976e22..be0329b7fd5a 100644 --- a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/PrintMessageMiddlewareCodeSnippet.cs +++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/PrintMessageMiddlewareCodeSnippet.cs @@ -2,8 +2,8 @@ // PrintMessageMiddlewareCodeSnippet.cs using AutoGen.Core; -using AutoGen.OpenAI; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1; +using AutoGen.OpenAI.V1.Extension; using Azure; using Azure.AI.OpenAI; diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/TypeSafeFunctionCallCodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/TypeSafeFunctionCallCodeSnippet.cs index 50bcd8a8048e..cf3e25eeee40 100644 --- a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/TypeSafeFunctionCallCodeSnippet.cs +++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/TypeSafeFunctionCallCodeSnippet.cs @@ -2,7 +2,7 @@ // TypeSafeFunctionCallCodeSnippet.cs using System.Text.Json; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1.Extension; using Azure.AI.OpenAI; #region weather_report_using_statement using AutoGen.Core; diff --git a/dotnet/sample/AutoGen.BasicSamples/Example04_Dynamic_GroupChat_Coding_Task.cs b/dotnet/sample/AutoGen.BasicSamples/Example04_Dynamic_GroupChat_Coding_Task.cs index 08419d436e03..32f06136a964 100644 --- a/dotnet/sample/AutoGen.BasicSamples/Example04_Dynamic_GroupChat_Coding_Task.cs +++ b/dotnet/sample/AutoGen.BasicSamples/Example04_Dynamic_GroupChat_Coding_Task.cs @@ -6,7 +6,7 @@ using AutoGen.Core; using AutoGen.DotnetInteractive; using AutoGen.DotnetInteractive.Extension; -using AutoGen.OpenAI; +using AutoGen.OpenAI.V1; using FluentAssertions; public partial class Example04_Dynamic_GroupChat_Coding_Task diff --git a/dotnet/sample/AutoGen.BasicSamples/Example05_Dalle_And_GPT4V.cs b/dotnet/sample/AutoGen.BasicSamples/Example05_Dalle_And_GPT4V.cs index ba7b5d4bde44..863f477630de 100644 --- a/dotnet/sample/AutoGen.BasicSamples/Example05_Dalle_And_GPT4V.cs +++ b/dotnet/sample/AutoGen.BasicSamples/Example05_Dalle_And_GPT4V.cs @@ -2,8 +2,8 @@ // Example05_Dalle_And_GPT4V.cs using AutoGen.Core; -using AutoGen.OpenAI; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1; +using AutoGen.OpenAI.V1.Extension; using Azure.AI.OpenAI; using FluentAssertions; using autogen = AutoGen.LLMConfigAPI; diff --git a/dotnet/sample/AutoGen.BasicSamples/Example06_UserProxyAgent.cs b/dotnet/sample/AutoGen.BasicSamples/Example06_UserProxyAgent.cs index dd3b5a671921..9e1cf42b48f5 100644 --- a/dotnet/sample/AutoGen.BasicSamples/Example06_UserProxyAgent.cs +++ b/dotnet/sample/AutoGen.BasicSamples/Example06_UserProxyAgent.cs @@ -1,7 +1,7 @@ ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. // Example06_UserProxyAgent.cs using AutoGen.Core; -using AutoGen.OpenAI; +using AutoGen.OpenAI.V1; namespace AutoGen.BasicSample; diff --git a/dotnet/sample/AutoGen.BasicSamples/Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs b/dotnet/sample/AutoGen.BasicSamples/Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs index cc9b2a80a340..f4fd98c3d03d 100644 --- a/dotnet/sample/AutoGen.BasicSamples/Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs +++ b/dotnet/sample/AutoGen.BasicSamples/Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs @@ -7,8 +7,8 @@ using AutoGen.Core; using AutoGen.DotnetInteractive; using AutoGen.DotnetInteractive.Extension; -using AutoGen.OpenAI; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1; +using AutoGen.OpenAI.V1.Extension; using Azure.AI.OpenAI; using Microsoft.DotNet.Interactive; diff --git a/dotnet/sample/AutoGen.BasicSamples/Example09_LMStudio_FunctionCall.cs b/dotnet/sample/AutoGen.BasicSamples/Example09_LMStudio_FunctionCall.cs index 9e20e24df761..afa7d43b975b 100644 --- a/dotnet/sample/AutoGen.BasicSamples/Example09_LMStudio_FunctionCall.cs +++ b/dotnet/sample/AutoGen.BasicSamples/Example09_LMStudio_FunctionCall.cs @@ -5,7 +5,7 @@ using System.Text.Json.Serialization; using AutoGen.Core; using AutoGen.LMStudio; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1.Extension; using Azure.AI.OpenAI; namespace AutoGen.BasicSample; diff --git a/dotnet/sample/AutoGen.BasicSamples/Example11_Sequential_GroupChat_Example.cs b/dotnet/sample/AutoGen.BasicSamples/Example11_Sequential_GroupChat_Example.cs index 00ff321082a4..6cb6b76ac88b 100644 --- a/dotnet/sample/AutoGen.BasicSamples/Example11_Sequential_GroupChat_Example.cs +++ b/dotnet/sample/AutoGen.BasicSamples/Example11_Sequential_GroupChat_Example.cs @@ -3,8 +3,8 @@ #region using_statement using AutoGen.Core; -using AutoGen.OpenAI; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1; +using AutoGen.OpenAI.V1.Extension; using AutoGen.SemanticKernel; using AutoGen.SemanticKernel.Extension; using Azure.AI.OpenAI; diff --git a/dotnet/sample/AutoGen.BasicSamples/Example12_TwoAgent_Fill_Application.cs b/dotnet/sample/AutoGen.BasicSamples/Example12_TwoAgent_Fill_Application.cs index ce585f229dc3..7aec3beee6b6 100644 --- a/dotnet/sample/AutoGen.BasicSamples/Example12_TwoAgent_Fill_Application.cs +++ b/dotnet/sample/AutoGen.BasicSamples/Example12_TwoAgent_Fill_Application.cs @@ -3,8 +3,8 @@ using System.Text; using AutoGen.Core; -using AutoGen.OpenAI; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1; +using AutoGen.OpenAI.V1.Extension; using Azure.AI.OpenAI; namespace AutoGen.BasicSample; diff --git a/dotnet/sample/AutoGen.BasicSamples/Example15_GPT4V_BinaryDataImageMessage.cs b/dotnet/sample/AutoGen.BasicSamples/Example15_GPT4V_BinaryDataImageMessage.cs index dee9915511d6..5d4f1474232d 100644 --- a/dotnet/sample/AutoGen.BasicSamples/Example15_GPT4V_BinaryDataImageMessage.cs +++ b/dotnet/sample/AutoGen.BasicSamples/Example15_GPT4V_BinaryDataImageMessage.cs @@ -2,7 +2,7 @@ // Example15_GPT4V_BinaryDataImageMessage.cs using AutoGen.Core; -using AutoGen.OpenAI; +using AutoGen.OpenAI.V1; namespace AutoGen.BasicSample; diff --git a/dotnet/sample/AutoGen.BasicSamples/Example17_ReActAgent.cs b/dotnet/sample/AutoGen.BasicSamples/Example17_ReActAgent.cs index f598ebbf7c46..5f50b2344170 100644 --- a/dotnet/sample/AutoGen.BasicSamples/Example17_ReActAgent.cs +++ b/dotnet/sample/AutoGen.BasicSamples/Example17_ReActAgent.cs @@ -2,8 +2,8 @@ // Example17_ReActAgent.cs using AutoGen.Core; -using AutoGen.OpenAI; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1; +using AutoGen.OpenAI.V1.Extension; using Azure.AI.OpenAI; namespace AutoGen.BasicSample; diff --git a/dotnet/sample/AutoGen.BasicSamples/GettingStart/Agent_Middleware.cs b/dotnet/sample/AutoGen.BasicSamples/GettingStart/Agent_Middleware.cs index 57f8ab4075c2..73c0332c7856 100644 --- a/dotnet/sample/AutoGen.BasicSamples/GettingStart/Agent_Middleware.cs +++ b/dotnet/sample/AutoGen.BasicSamples/GettingStart/Agent_Middleware.cs @@ -3,8 +3,8 @@ #region Using using AutoGen.Core; -using AutoGen.OpenAI; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1; +using AutoGen.OpenAI.V1.Extension; using Azure.AI.OpenAI; #endregion Using using FluentAssertions; diff --git a/dotnet/sample/AutoGen.BasicSamples/GettingStart/Chat_With_Agent.cs b/dotnet/sample/AutoGen.BasicSamples/GettingStart/Chat_With_Agent.cs index 0ac1cda75288..1b92572821b5 100644 --- a/dotnet/sample/AutoGen.BasicSamples/GettingStart/Chat_With_Agent.cs +++ b/dotnet/sample/AutoGen.BasicSamples/GettingStart/Chat_With_Agent.cs @@ -3,8 +3,8 @@ #region Using using AutoGen.Core; -using AutoGen.OpenAI; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1; +using AutoGen.OpenAI.V1.Extension; using Azure.AI.OpenAI; #endregion Using diff --git a/dotnet/sample/AutoGen.BasicSamples/GettingStart/Dynamic_Group_Chat.cs b/dotnet/sample/AutoGen.BasicSamples/GettingStart/Dynamic_Group_Chat.cs index 7acaae4b1f82..865924ca7d06 100644 --- a/dotnet/sample/AutoGen.BasicSamples/GettingStart/Dynamic_Group_Chat.cs +++ b/dotnet/sample/AutoGen.BasicSamples/GettingStart/Dynamic_Group_Chat.cs @@ -2,8 +2,8 @@ // Dynamic_Group_Chat.cs using AutoGen.Core; -using AutoGen.OpenAI; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1; +using AutoGen.OpenAI.V1.Extension; using AutoGen.SemanticKernel; using AutoGen.SemanticKernel.Extension; using Azure.AI.OpenAI; diff --git a/dotnet/sample/AutoGen.BasicSamples/GettingStart/FSM_Group_Chat.cs b/dotnet/sample/AutoGen.BasicSamples/GettingStart/FSM_Group_Chat.cs index ac1a1934f077..28b8f5d5fbdc 100644 --- a/dotnet/sample/AutoGen.BasicSamples/GettingStart/FSM_Group_Chat.cs +++ b/dotnet/sample/AutoGen.BasicSamples/GettingStart/FSM_Group_Chat.cs @@ -4,8 +4,8 @@ using System.Text; #region Using using AutoGen.Core; -using AutoGen.OpenAI; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1; +using AutoGen.OpenAI.V1.Extension; using Azure.AI.OpenAI; #endregion Using diff --git a/dotnet/sample/AutoGen.BasicSamples/GettingStart/Image_Chat_With_Agent.cs b/dotnet/sample/AutoGen.BasicSamples/GettingStart/Image_Chat_With_Agent.cs index 5b94a238bbe8..5e2aff061ae5 100644 --- a/dotnet/sample/AutoGen.BasicSamples/GettingStart/Image_Chat_With_Agent.cs +++ b/dotnet/sample/AutoGen.BasicSamples/GettingStart/Image_Chat_With_Agent.cs @@ -3,8 +3,8 @@ #region Using using AutoGen.Core; -using AutoGen.OpenAI; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1; +using AutoGen.OpenAI.V1.Extension; using Azure.AI.OpenAI; #endregion Using using FluentAssertions; diff --git a/dotnet/sample/AutoGen.BasicSamples/GettingStart/Streaming_Tool_Call.cs b/dotnet/sample/AutoGen.BasicSamples/GettingStart/Streaming_Tool_Call.cs index 48ebd127b562..d358dab60fe1 100644 --- a/dotnet/sample/AutoGen.BasicSamples/GettingStart/Streaming_Tool_Call.cs +++ b/dotnet/sample/AutoGen.BasicSamples/GettingStart/Streaming_Tool_Call.cs @@ -2,8 +2,8 @@ // Streaming_Tool_Call.cs using AutoGen.Core; -using AutoGen.OpenAI; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1; +using AutoGen.OpenAI.V1.Extension; using Azure.AI.OpenAI; using FluentAssertions; diff --git a/dotnet/sample/AutoGen.BasicSamples/GettingStart/Use_Tools_With_Agent.cs b/dotnet/sample/AutoGen.BasicSamples/GettingStart/Use_Tools_With_Agent.cs index b441fe389da2..f1a7b5585daa 100644 --- a/dotnet/sample/AutoGen.BasicSamples/GettingStart/Use_Tools_With_Agent.cs +++ b/dotnet/sample/AutoGen.BasicSamples/GettingStart/Use_Tools_With_Agent.cs @@ -3,8 +3,8 @@ #region Using using AutoGen.Core; -using AutoGen.OpenAI; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1; +using AutoGen.OpenAI.V1.Extension; using Azure.AI.OpenAI; #endregion Using using FluentAssertions; diff --git a/dotnet/sample/AutoGen.BasicSamples/LLMConfiguration.cs b/dotnet/sample/AutoGen.BasicSamples/LLMConfiguration.cs index e492569cdc3d..bab6685126a7 100644 --- a/dotnet/sample/AutoGen.BasicSamples/LLMConfiguration.cs +++ b/dotnet/sample/AutoGen.BasicSamples/LLMConfiguration.cs @@ -1,7 +1,7 @@ ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. // LLMConfiguration.cs -using AutoGen.OpenAI; +using AutoGen.OpenAI.V1; namespace AutoGen.BasicSample; diff --git a/dotnet/sample/AutoGen.OpenAI.Sample/AutoGen.OpenAI.Sample.csproj b/dotnet/sample/AutoGen.OpenAI.Sample/AutoGen.OpenAI.V1.Sample.csproj similarity index 100% rename from dotnet/sample/AutoGen.OpenAI.Sample/AutoGen.OpenAI.Sample.csproj rename to dotnet/sample/AutoGen.OpenAI.Sample/AutoGen.OpenAI.V1.Sample.csproj diff --git a/dotnet/sample/AutoGen.OpenAI.Sample/Connect_To_Ollama.cs b/dotnet/sample/AutoGen.OpenAI.Sample/Connect_To_Ollama.cs index 3823de2a5284..bfc4446b2ee9 100644 --- a/dotnet/sample/AutoGen.OpenAI.Sample/Connect_To_Ollama.cs +++ b/dotnet/sample/AutoGen.OpenAI.Sample/Connect_To_Ollama.cs @@ -3,7 +3,8 @@ #region using_statement using AutoGen.Core; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1; +using AutoGen.OpenAI.V1.Extension; using Azure.AI.OpenAI; using Azure.Core.Pipeline; #endregion using_statement diff --git a/dotnet/sample/AutoGen.OpenAI.Sample/Tool_Call_With_Ollama_And_LiteLLM.cs b/dotnet/sample/AutoGen.OpenAI.Sample/Tool_Call_With_Ollama_And_LiteLLM.cs index b0b0adc0e6f5..d9a963388d70 100644 --- a/dotnet/sample/AutoGen.OpenAI.Sample/Tool_Call_With_Ollama_And_LiteLLM.cs +++ b/dotnet/sample/AutoGen.OpenAI.Sample/Tool_Call_With_Ollama_And_LiteLLM.cs @@ -2,7 +2,8 @@ // Tool_Call_With_Ollama_And_LiteLLM.cs using AutoGen.Core; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1; +using AutoGen.OpenAI.V1.Extension; using Azure.AI.OpenAI; using Azure.Core.Pipeline; diff --git a/dotnet/sample/AutoGen.OpenAI.Sample/Use_Json_Mode.cs b/dotnet/sample/AutoGen.OpenAI.Sample/Use_Json_Mode.cs index d92983c5050f..0def9c4ca8e2 100644 --- a/dotnet/sample/AutoGen.OpenAI.Sample/Use_Json_Mode.cs +++ b/dotnet/sample/AutoGen.OpenAI.Sample/Use_Json_Mode.cs @@ -4,8 +4,8 @@ using System.Text.Json; using System.Text.Json.Serialization; using AutoGen.Core; -using AutoGen.OpenAI; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1; +using AutoGen.OpenAI.V1.Extension; using Azure.AI.OpenAI; using FluentAssertions; diff --git a/dotnet/sample/AutoGen.SemanticKernel.Sample/Use_Kernel_Functions_With_Other_Agent.cs b/dotnet/sample/AutoGen.SemanticKernel.Sample/Use_Kernel_Functions_With_Other_Agent.cs index 2beb1ee7df0a..688f3b017a68 100644 --- a/dotnet/sample/AutoGen.SemanticKernel.Sample/Use_Kernel_Functions_With_Other_Agent.cs +++ b/dotnet/sample/AutoGen.SemanticKernel.Sample/Use_Kernel_Functions_With_Other_Agent.cs @@ -3,8 +3,8 @@ #region Using using AutoGen.Core; -using AutoGen.OpenAI; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1; +using AutoGen.OpenAI.V1.Extension; using Azure.AI.OpenAI; using Microsoft.SemanticKernel; #endregion Using diff --git a/dotnet/src/AutoGen.LMStudio/AutoGen.LMStudio.csproj b/dotnet/src/AutoGen.LMStudio/AutoGen.LMStudio.csproj index 8725d564df41..aa891e71294d 100644 --- a/dotnet/src/AutoGen.LMStudio/AutoGen.LMStudio.csproj +++ b/dotnet/src/AutoGen.LMStudio/AutoGen.LMStudio.csproj @@ -17,7 +17,7 @@ - + diff --git a/dotnet/src/AutoGen.LMStudio/LMStudioAgent.cs b/dotnet/src/AutoGen.LMStudio/LMStudioAgent.cs index c3930abc0def..52a5d9bfdd0f 100644 --- a/dotnet/src/AutoGen.LMStudio/LMStudioAgent.cs +++ b/dotnet/src/AutoGen.LMStudio/LMStudioAgent.cs @@ -6,7 +6,7 @@ using System.Net.Http; using System.Threading; using System.Threading.Tasks; -using AutoGen.OpenAI; +using AutoGen.OpenAI.V1; using Azure.AI.OpenAI; using Azure.Core.Pipeline; diff --git a/dotnet/src/AutoGen.OpenAI/Agent/GPTAgent.cs b/dotnet/src/AutoGen.OpenAI.V1/Agent/GPTAgent.cs similarity index 98% rename from dotnet/src/AutoGen.OpenAI/Agent/GPTAgent.cs rename to dotnet/src/AutoGen.OpenAI.V1/Agent/GPTAgent.cs index 5de481245b72..8d6458299f2d 100644 --- a/dotnet/src/AutoGen.OpenAI/Agent/GPTAgent.cs +++ b/dotnet/src/AutoGen.OpenAI.V1/Agent/GPTAgent.cs @@ -5,10 +5,10 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1.Extension; using Azure.AI.OpenAI; -namespace AutoGen.OpenAI; +namespace AutoGen.OpenAI.V1; /// /// GPT agent that can be used to connect to OpenAI chat models like GPT-3.5, GPT-4, etc. diff --git a/dotnet/src/AutoGen.OpenAI/Agent/OpenAIChatAgent.cs b/dotnet/src/AutoGen.OpenAI.V1/Agent/OpenAIChatAgent.cs similarity index 99% rename from dotnet/src/AutoGen.OpenAI/Agent/OpenAIChatAgent.cs rename to dotnet/src/AutoGen.OpenAI.V1/Agent/OpenAIChatAgent.cs index c957801f0238..2305536b4e5d 100644 --- a/dotnet/src/AutoGen.OpenAI/Agent/OpenAIChatAgent.cs +++ b/dotnet/src/AutoGen.OpenAI.V1/Agent/OpenAIChatAgent.cs @@ -8,10 +8,10 @@ using System.Text.Json; using System.Threading; using System.Threading.Tasks; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1.Extension; using Azure.AI.OpenAI; -namespace AutoGen.OpenAI; +namespace AutoGen.OpenAI.V1; /// /// OpenAI client agent. This agent is a thin wrapper around to provide a simple interface for chat completions. diff --git a/dotnet/src/AutoGen.OpenAI/AutoGen.OpenAI.csproj b/dotnet/src/AutoGen.OpenAI.V1/AutoGen.OpenAI.V1.csproj similarity index 100% rename from dotnet/src/AutoGen.OpenAI/AutoGen.OpenAI.csproj rename to dotnet/src/AutoGen.OpenAI.V1/AutoGen.OpenAI.V1.csproj diff --git a/dotnet/src/AutoGen.OpenAI/AzureOpenAIConfig.cs b/dotnet/src/AutoGen.OpenAI.V1/AzureOpenAIConfig.cs similarity index 95% rename from dotnet/src/AutoGen.OpenAI/AzureOpenAIConfig.cs rename to dotnet/src/AutoGen.OpenAI.V1/AzureOpenAIConfig.cs index 31df784ed21a..2be8f21dc4fc 100644 --- a/dotnet/src/AutoGen.OpenAI/AzureOpenAIConfig.cs +++ b/dotnet/src/AutoGen.OpenAI.V1/AzureOpenAIConfig.cs @@ -1,7 +1,7 @@ ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. // AzureOpenAIConfig.cs -namespace AutoGen.OpenAI; +namespace AutoGen.OpenAI.V1; public class AzureOpenAIConfig : ILLMConfig { diff --git a/dotnet/src/AutoGen.OpenAI/Extension/FunctionContractExtension.cs b/dotnet/src/AutoGen.OpenAI.V1/Extension/FunctionContractExtension.cs similarity index 98% rename from dotnet/src/AutoGen.OpenAI/Extension/FunctionContractExtension.cs rename to dotnet/src/AutoGen.OpenAI.V1/Extension/FunctionContractExtension.cs index 4accdc4d8d46..62009b927eff 100644 --- a/dotnet/src/AutoGen.OpenAI/Extension/FunctionContractExtension.cs +++ b/dotnet/src/AutoGen.OpenAI.V1/Extension/FunctionContractExtension.cs @@ -7,7 +7,7 @@ using Json.Schema; using Json.Schema.Generation; -namespace AutoGen.OpenAI.Extension; +namespace AutoGen.OpenAI.V1.Extension; public static class FunctionContractExtension { diff --git a/dotnet/src/AutoGen.OpenAI/Extension/MessageExtension.cs b/dotnet/src/AutoGen.OpenAI.V1/Extension/MessageExtension.cs similarity index 99% rename from dotnet/src/AutoGen.OpenAI/Extension/MessageExtension.cs rename to dotnet/src/AutoGen.OpenAI.V1/Extension/MessageExtension.cs index ed795e5e8ed8..3264dccf3a8a 100644 --- a/dotnet/src/AutoGen.OpenAI/Extension/MessageExtension.cs +++ b/dotnet/src/AutoGen.OpenAI.V1/Extension/MessageExtension.cs @@ -6,7 +6,7 @@ using System.Linq; using Azure.AI.OpenAI; -namespace AutoGen.OpenAI; +namespace AutoGen.OpenAI.V1; public static class MessageExtension { diff --git a/dotnet/src/AutoGen.OpenAI/Extension/OpenAIAgentExtension.cs b/dotnet/src/AutoGen.OpenAI.V1/Extension/OpenAIAgentExtension.cs similarity index 97% rename from dotnet/src/AutoGen.OpenAI/Extension/OpenAIAgentExtension.cs rename to dotnet/src/AutoGen.OpenAI.V1/Extension/OpenAIAgentExtension.cs index 1e8ae58954ea..6c0df8e0e965 100644 --- a/dotnet/src/AutoGen.OpenAI/Extension/OpenAIAgentExtension.cs +++ b/dotnet/src/AutoGen.OpenAI.V1/Extension/OpenAIAgentExtension.cs @@ -1,7 +1,7 @@ ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. // OpenAIAgentExtension.cs -namespace AutoGen.OpenAI.Extension; +namespace AutoGen.OpenAI.V1.Extension; public static class OpenAIAgentExtension { diff --git a/dotnet/src/AutoGen.OpenAI/GlobalUsing.cs b/dotnet/src/AutoGen.OpenAI.V1/GlobalUsing.cs similarity index 100% rename from dotnet/src/AutoGen.OpenAI/GlobalUsing.cs rename to dotnet/src/AutoGen.OpenAI.V1/GlobalUsing.cs diff --git a/dotnet/src/AutoGen.OpenAI/Middleware/OpenAIChatRequestMessageConnector.cs b/dotnet/src/AutoGen.OpenAI.V1/Middleware/OpenAIChatRequestMessageConnector.cs similarity index 99% rename from dotnet/src/AutoGen.OpenAI/Middleware/OpenAIChatRequestMessageConnector.cs rename to dotnet/src/AutoGen.OpenAI.V1/Middleware/OpenAIChatRequestMessageConnector.cs index e1dd0757fcf3..f1bea485c1c1 100644 --- a/dotnet/src/AutoGen.OpenAI/Middleware/OpenAIChatRequestMessageConnector.cs +++ b/dotnet/src/AutoGen.OpenAI.V1/Middleware/OpenAIChatRequestMessageConnector.cs @@ -9,7 +9,7 @@ using System.Threading.Tasks; using Azure.AI.OpenAI; -namespace AutoGen.OpenAI; +namespace AutoGen.OpenAI.V1; /// /// This middleware converts the incoming to where T is before sending to agent. And converts the output to after receiving from agent. diff --git a/dotnet/src/AutoGen.OpenAI/OpenAIConfig.cs b/dotnet/src/AutoGen.OpenAI.V1/OpenAIConfig.cs similarity index 91% rename from dotnet/src/AutoGen.OpenAI/OpenAIConfig.cs rename to dotnet/src/AutoGen.OpenAI.V1/OpenAIConfig.cs index 35ce1e491aa9..592647cc2c1e 100644 --- a/dotnet/src/AutoGen.OpenAI/OpenAIConfig.cs +++ b/dotnet/src/AutoGen.OpenAI.V1/OpenAIConfig.cs @@ -1,7 +1,7 @@ ο»Ώ// Copyright (c) Microsoft Corporation. All rights reserved. // OpenAIConfig.cs -namespace AutoGen.OpenAI; +namespace AutoGen.OpenAI.V1; public class OpenAIConfig : ILLMConfig { diff --git a/dotnet/src/AutoGen.SemanticKernel/AutoGen.SemanticKernel.csproj b/dotnet/src/AutoGen.SemanticKernel/AutoGen.SemanticKernel.csproj index 1cc4d8e127a7..8769c3ac4879 100644 --- a/dotnet/src/AutoGen.SemanticKernel/AutoGen.SemanticKernel.csproj +++ b/dotnet/src/AutoGen.SemanticKernel/AutoGen.SemanticKernel.csproj @@ -17,7 +17,6 @@ - diff --git a/dotnet/src/AutoGen/API/LLMConfigAPI.cs b/dotnet/src/AutoGen/API/LLMConfigAPI.cs index 5154f3dd5f55..fcc744351731 100644 --- a/dotnet/src/AutoGen/API/LLMConfigAPI.cs +++ b/dotnet/src/AutoGen/API/LLMConfigAPI.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; -using AutoGen.OpenAI; +using AutoGen.OpenAI.V1; namespace AutoGen { diff --git a/dotnet/src/AutoGen/Agent/ConversableAgent.cs b/dotnet/src/AutoGen/Agent/ConversableAgent.cs index fe1470502022..b60d2eba099d 100644 --- a/dotnet/src/AutoGen/Agent/ConversableAgent.cs +++ b/dotnet/src/AutoGen/Agent/ConversableAgent.cs @@ -7,7 +7,7 @@ using System.Threading; using System.Threading.Tasks; using AutoGen.LMStudio; -using AutoGen.OpenAI; +using AutoGen.OpenAI.V1; namespace AutoGen; diff --git a/dotnet/src/AutoGen/AutoGen.csproj b/dotnet/src/AutoGen/AutoGen.csproj index 88d9fca19ca2..4c3b2a5ab81e 100644 --- a/dotnet/src/AutoGen/AutoGen.csproj +++ b/dotnet/src/AutoGen/AutoGen.csproj @@ -27,7 +27,7 @@ - + diff --git a/dotnet/test/AutoGen.OpenAI.Tests/ApprovalTests/OpenAIMessageTests.BasicMessageTest.approved.txt b/dotnet/test/AutoGen.OpenAI.V1.Tests/ApprovalTests/OpenAIMessageTests.BasicMessageTest.approved.txt similarity index 100% rename from dotnet/test/AutoGen.OpenAI.Tests/ApprovalTests/OpenAIMessageTests.BasicMessageTest.approved.txt rename to dotnet/test/AutoGen.OpenAI.V1.Tests/ApprovalTests/OpenAIMessageTests.BasicMessageTest.approved.txt diff --git a/dotnet/test/AutoGen.OpenAI.Tests/AutoGen.OpenAI.Tests.csproj b/dotnet/test/AutoGen.OpenAI.V1.Tests/AutoGen.OpenAI.V1.Tests.csproj similarity index 96% rename from dotnet/test/AutoGen.OpenAI.Tests/AutoGen.OpenAI.Tests.csproj rename to dotnet/test/AutoGen.OpenAI.V1.Tests/AutoGen.OpenAI.V1.Tests.csproj index b176bc3e6ac2..74d7d7b0a16f 100644 --- a/dotnet/test/AutoGen.OpenAI.Tests/AutoGen.OpenAI.Tests.csproj +++ b/dotnet/test/AutoGen.OpenAI.V1.Tests/AutoGen.OpenAI.V1.Tests.csproj @@ -8,7 +8,7 @@ - + diff --git a/dotnet/test/AutoGen.OpenAI.Tests/GlobalUsing.cs b/dotnet/test/AutoGen.OpenAI.V1.Tests/GlobalUsing.cs similarity index 100% rename from dotnet/test/AutoGen.OpenAI.Tests/GlobalUsing.cs rename to dotnet/test/AutoGen.OpenAI.V1.Tests/GlobalUsing.cs diff --git a/dotnet/test/AutoGen.OpenAI.Tests/MathClassTest.cs b/dotnet/test/AutoGen.OpenAI.V1.Tests/MathClassTest.cs similarity index 99% rename from dotnet/test/AutoGen.OpenAI.Tests/MathClassTest.cs rename to dotnet/test/AutoGen.OpenAI.V1.Tests/MathClassTest.cs index 01af3d4646c4..a1f9541f467d 100644 --- a/dotnet/test/AutoGen.OpenAI.Tests/MathClassTest.cs +++ b/dotnet/test/AutoGen.OpenAI.V1.Tests/MathClassTest.cs @@ -6,13 +6,13 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1.Extension; using AutoGen.Tests; using Azure.AI.OpenAI; using FluentAssertions; using Xunit.Abstractions; -namespace AutoGen.OpenAI.Tests +namespace AutoGen.OpenAI.V1.Tests { public partial class MathClassTest { diff --git a/dotnet/test/AutoGen.OpenAI.Tests/OpenAIChatAgentTest.cs b/dotnet/test/AutoGen.OpenAI.V1.Tests/OpenAIChatAgentTest.cs similarity index 99% rename from dotnet/test/AutoGen.OpenAI.Tests/OpenAIChatAgentTest.cs rename to dotnet/test/AutoGen.OpenAI.V1.Tests/OpenAIChatAgentTest.cs index 85f898547b00..0957cc9f49bb 100644 --- a/dotnet/test/AutoGen.OpenAI.Tests/OpenAIChatAgentTest.cs +++ b/dotnet/test/AutoGen.OpenAI.V1.Tests/OpenAIChatAgentTest.cs @@ -5,12 +5,12 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1.Extension; using AutoGen.Tests; using Azure.AI.OpenAI; using FluentAssertions; -namespace AutoGen.OpenAI.Tests; +namespace AutoGen.OpenAI.V1.Tests; public partial class OpenAIChatAgentTest { diff --git a/dotnet/test/AutoGen.OpenAI.Tests/OpenAIMessageTests.cs b/dotnet/test/AutoGen.OpenAI.V1.Tests/OpenAIMessageTests.cs similarity index 99% rename from dotnet/test/AutoGen.OpenAI.Tests/OpenAIMessageTests.cs rename to dotnet/test/AutoGen.OpenAI.V1.Tests/OpenAIMessageTests.cs index a9b852e0d8c1..3050c4e8e09b 100644 --- a/dotnet/test/AutoGen.OpenAI.Tests/OpenAIMessageTests.cs +++ b/dotnet/test/AutoGen.OpenAI.V1.Tests/OpenAIMessageTests.cs @@ -15,7 +15,7 @@ using FluentAssertions; using Xunit; -namespace AutoGen.OpenAI.Tests; +namespace AutoGen.OpenAI.V1.Tests; public class OpenAIMessageTests { diff --git a/dotnet/test/AutoGen.SemanticKernel.Tests/KernelFunctionMiddlewareTests.cs b/dotnet/test/AutoGen.SemanticKernel.Tests/KernelFunctionMiddlewareTests.cs index 3e955c8ecbcc..3732cd0197bd 100644 --- a/dotnet/test/AutoGen.SemanticKernel.Tests/KernelFunctionMiddlewareTests.cs +++ b/dotnet/test/AutoGen.SemanticKernel.Tests/KernelFunctionMiddlewareTests.cs @@ -2,8 +2,8 @@ // KernelFunctionMiddlewareTests.cs using AutoGen.Core; -using AutoGen.OpenAI; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1; +using AutoGen.OpenAI.V1.Extension; using AutoGen.Tests; using Azure.AI.OpenAI; using FluentAssertions; diff --git a/dotnet/test/AutoGen.SourceGenerator.Tests/FunctionExample.test.cs b/dotnet/test/AutoGen.SourceGenerator.Tests/FunctionExample.test.cs index 0096f2c157ce..066aff8156a9 100644 --- a/dotnet/test/AutoGen.SourceGenerator.Tests/FunctionExample.test.cs +++ b/dotnet/test/AutoGen.SourceGenerator.Tests/FunctionExample.test.cs @@ -5,7 +5,7 @@ using ApprovalTests; using ApprovalTests.Namers; using ApprovalTests.Reporters; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1.Extension; using Azure.AI.OpenAI; using FluentAssertions; using Xunit; diff --git a/dotnet/test/AutoGen.Tests/Orchestrator/RolePlayOrchestratorTests.cs b/dotnet/test/AutoGen.Tests/Orchestrator/RolePlayOrchestratorTests.cs index f9ab09716b94..a91524257b66 100644 --- a/dotnet/test/AutoGen.Tests/Orchestrator/RolePlayOrchestratorTests.cs +++ b/dotnet/test/AutoGen.Tests/Orchestrator/RolePlayOrchestratorTests.cs @@ -15,8 +15,8 @@ using AutoGen.Gemini; using AutoGen.Mistral; using AutoGen.Mistral.Extension; -using AutoGen.OpenAI; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1; +using AutoGen.OpenAI.V1.Extension; using Azure.AI.Inference; using Azure.AI.OpenAI; using FluentAssertions; diff --git a/dotnet/test/AutoGen.Tests/SingleAgentTest.cs b/dotnet/test/AutoGen.Tests/SingleAgentTest.cs index b545bbdbe864..b0633d429b2c 100644 --- a/dotnet/test/AutoGen.Tests/SingleAgentTest.cs +++ b/dotnet/test/AutoGen.Tests/SingleAgentTest.cs @@ -7,8 +7,8 @@ using System.Linq; using System.Threading.Tasks; using AutoGen.LMStudio; -using AutoGen.OpenAI; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1; +using AutoGen.OpenAI.V1.Extension; using Azure.AI.OpenAI; using FluentAssertions; using Xunit; diff --git a/dotnet/test/AutoGen.Tests/TwoAgentTest.cs b/dotnet/test/AutoGen.Tests/TwoAgentTest.cs index 100a22c04a71..b5d7bceed4dd 100644 --- a/dotnet/test/AutoGen.Tests/TwoAgentTest.cs +++ b/dotnet/test/AutoGen.Tests/TwoAgentTest.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using AutoGen.OpenAI; +using AutoGen.OpenAI.V1; using FluentAssertions; using Xunit.Abstractions; diff --git a/dotnet/test/AutoGen.WebAPI.Tests/OpenAIChatCompletionMiddlewareTests.cs b/dotnet/test/AutoGen.WebAPI.Tests/OpenAIChatCompletionMiddlewareTests.cs index 07bdc850936d..964f1fc94edb 100644 --- a/dotnet/test/AutoGen.WebAPI.Tests/OpenAIChatCompletionMiddlewareTests.cs +++ b/dotnet/test/AutoGen.WebAPI.Tests/OpenAIChatCompletionMiddlewareTests.cs @@ -2,8 +2,8 @@ // OpenAIChatCompletionMiddlewareTests.cs using AutoGen.Core; -using AutoGen.OpenAI; -using AutoGen.OpenAI.Extension; +using AutoGen.OpenAI.V1; +using AutoGen.OpenAI.V1.Extension; using Azure.AI.OpenAI; using Azure.Core.Pipeline; using FluentAssertions; From 395af4e258266e7fed4deaf031772657a3115355 Mon Sep 17 00:00:00 2001 From: New-World-2019 <37373361+New-World-2019@users.noreply.github.com> Date: Thu, 22 Aug 2024 17:24:25 +0800 Subject: [PATCH 06/16] =?UTF-8?q?Update=20Docker.md=EF=BC=9Bfix=20broken?= =?UTF-8?q?=20URL=20(#3399)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This pull request includes a minor update to the CONTRIBUTORS.md file to correct the link to the Dockerfile README. --- website/docs/installation/Docker.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/installation/Docker.md b/website/docs/installation/Docker.md index 4857f4f880c6..a7fa6bd829b5 100644 --- a/website/docs/installation/Docker.md +++ b/website/docs/installation/Docker.md @@ -82,6 +82,6 @@ docker run -it -p {WorkstationPortNum}:{DockerPortNum} -v {WorkStation_Dir}:{Doc ## Additional Resources -- Details on all the Dockerfile options can be found in the [Dockerfile](https://github.com/microsoft/autogen/.devcontainer/README.md) README. +- Details on all the Dockerfile options can be found in the [Dockerfile](https://github.com/microsoft/autogen/blob/main/.devcontainer/README.md) README. - For more information on Docker usage and best practices, refer to the [official Docker documentation](https://docs.docker.com). - Details on how to use the Dockerfile dev version can be found on the [Contributor Guide](/docs/contributor-guide/docker). From 8aea3675589e656e3cd4dce299fb8c473af2f57d Mon Sep 17 00:00:00 2001 From: Eddy Fidel Date: Thu, 22 Aug 2024 05:38:34 -0400 Subject: [PATCH 07/16] Fix QdrantVectorDB to use custom embedding_function when provided, defaulting to FastEmbedEmbeddingFunction() otherwise (#3396) Co-authored-by: Li Jiang --- autogen/agentchat/contrib/vectordb/qdrant.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autogen/agentchat/contrib/vectordb/qdrant.py b/autogen/agentchat/contrib/vectordb/qdrant.py index 398734eb0334..d9c4ee1d2e5a 100644 --- a/autogen/agentchat/contrib/vectordb/qdrant.py +++ b/autogen/agentchat/contrib/vectordb/qdrant.py @@ -93,7 +93,7 @@ def __init__( kwargs: dict | Additional keyword arguments. """ self.client: QdrantClient = client or QdrantClient(location=":memory:") - self.embedding_function = FastEmbedEmbeddingFunction() or embedding_function + self.embedding_function = embedding_function or FastEmbedEmbeddingFunction() self.collection_options = collection_options self.content_payload_key = content_payload_key self.metadata_payload_key = metadata_payload_key From 663092b5e13607098c4954ad789d6c6c90495a06 Mon Sep 17 00:00:00 2001 From: Li Jiang Date: Thu, 22 Aug 2024 17:58:08 +0800 Subject: [PATCH 08/16] Add mongodb to topic guide (#3400) --- website/docs/topics/retrieval_augmentation.md | 1 + 1 file changed, 1 insertion(+) diff --git a/website/docs/topics/retrieval_augmentation.md b/website/docs/topics/retrieval_augmentation.md index ebb09f0627e6..3c428f164868 100644 --- a/website/docs/topics/retrieval_augmentation.md +++ b/website/docs/topics/retrieval_augmentation.md @@ -125,6 +125,7 @@ For more detailed examples and notebooks showcasing the usage of retrieval augme - Automated Code Generation and Question Answering with Retrieval Augmented Agents - [View Notebook](/docs/notebooks/agentchat_RetrieveChat) - Automated Code Generation and Question Answering with [PGVector](https://github.com/pgvector/pgvector) based Retrieval Augmented Agents - [View Notebook](https://github.com/microsoft/autogen/blob/main/notebook/agentchat_RetrieveChat_pgvector.ipynb) - Automated Code Generation and Question Answering with [Qdrant](https://qdrant.tech/) based Retrieval Augmented Agents - [View Notebook](https://github.com/microsoft/autogen/blob/main/notebook/agentchat_RetrieveChat_qdrant.ipynb) +- Automated Code Generation and Question Answering with [MongoDB Atlas](https://www.mongodb.com/) based Retrieval Augmented Agents - [View Notebook](https://github.com/microsoft/autogen/blob/main/notebook/agentchat_RetrieveChat_mongodb.ipynb) - Chat with OpenAI Assistant with Retrieval Augmentation - [View Notebook](https://github.com/microsoft/autogen/blob/main/notebook/agentchat_oai_assistant_retrieval.ipynb) - **RAG**: Group Chat with Retrieval Augmented Generation (with 5 group member agents and 1 manager agent) - [View Notebook](/docs/notebooks/agentchat_groupchat_RAG) From 44516327841125fd5f0e1b073bc23c3dd3f6541e Mon Sep 17 00:00:00 2001 From: Mark Sze <66362098+marklysze@users.noreply.github.com> Date: Mon, 26 Aug 2024 13:47:48 +1000 Subject: [PATCH 09/16] Ability to add MessageTransforms to the GroupChat's Select Speaker nested chat (speaker_selection_method='auto') (#2719) * Initial commit with ability to add transforms to GroupChat * Added tests * Tidy up * Tidy up of variable names and commented out test * Tidy up comment * Update import to relative * Added documentation topic for transform messages for speaker selection. * Added handling of non-core module, transforms, in groupchat * Adjusted parameter if module not loaded. * Updated groupchat test which failed during CI/CD --------- Co-authored-by: Li Jiang --- .../capabilities/transform_messages.py | 3 +- autogen/agentchat/groupchat.py | 33 ++- test/agentchat/test_groupchat.py | 42 +++ ...transform_messages_speaker_selection.ipynb | 246 ++++++++++++++++++ 4 files changed, 321 insertions(+), 3 deletions(-) create mode 100644 website/docs/topics/groupchat/transform_messages_speaker_selection.ipynb diff --git a/autogen/agentchat/contrib/capabilities/transform_messages.py b/autogen/agentchat/contrib/capabilities/transform_messages.py index e96dc39fa7bc..1ce219bdadfa 100644 --- a/autogen/agentchat/contrib/capabilities/transform_messages.py +++ b/autogen/agentchat/contrib/capabilities/transform_messages.py @@ -1,9 +1,8 @@ import copy from typing import Dict, List -from autogen import ConversableAgent - from ....formatting_utils import colored +from ...conversable_agent import ConversableAgent from .transforms import MessageTransform diff --git a/autogen/agentchat/groupchat.py b/autogen/agentchat/groupchat.py index 53fc32e58d21..232503ad5684 100644 --- a/autogen/agentchat/groupchat.py +++ b/autogen/agentchat/groupchat.py @@ -5,7 +5,7 @@ import re import sys from dataclasses import dataclass, field -from typing import Callable, Dict, List, Literal, Optional, Tuple, Union +from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, Union from ..code_utils import content_str from ..exception_utils import AgentNameConflict, NoEligibleSpeaker, UndefinedNextAgent @@ -17,6 +17,12 @@ from .chat import ChatResult from .conversable_agent import ConversableAgent +try: + # Non-core module + from .contrib.capabilities import transform_messages +except ImportError: + transform_messages = None + logger = logging.getLogger(__name__) @@ -76,6 +82,8 @@ def custom_speaker_selection_func( of times until a single agent is returned or it exhausts the maximum attempts. Applies only to "auto" speaker selection method. Default is 2. + - select_speaker_transform_messages: (optional) the message transformations to apply to the nested select speaker agent-to-agent chat messages. + Takes a TransformMessages object, defaults to None and is only utilised when the speaker selection method is "auto". - select_speaker_auto_verbose: whether to output the select speaker responses and selections If set to True, the outputs from the two agents in the nested select speaker chat will be output, along with whether the responses were successful, or not, in selecting an agent @@ -132,6 +140,7 @@ def custom_speaker_selection_func( The names are case-sensitive and should not be abbreviated or changed. The only names that are accepted are {agentlist}. Respond with ONLY the name of the speaker and DO NOT provide a reason.""" + select_speaker_transform_messages: Optional[Any] = None select_speaker_auto_verbose: Optional[bool] = False role_for_select_speaker_messages: Optional[str] = "system" @@ -249,6 +258,20 @@ def __post_init__(self): elif self.max_retries_for_selecting_speaker < 0: raise ValueError("max_retries_for_selecting_speaker must be greater than or equal to zero") + # Load message transforms here (load once for the Group Chat so we don't have to re-initiate it and it maintains the cache across subsequent select speaker calls) + self._speaker_selection_transforms = None + if self.select_speaker_transform_messages is not None: + if transform_messages is not None: + if isinstance(self.select_speaker_transform_messages, transform_messages.TransformMessages): + self._speaker_selection_transforms = self.select_speaker_transform_messages + else: + raise ValueError("select_speaker_transform_messages must be None or MessageTransforms.") + else: + logger.warning( + "TransformMessages could not be loaded, the 'select_speaker_transform_messages' transform" + "will not apply." + ) + # Validate select_speaker_auto_verbose if self.select_speaker_auto_verbose is None or not isinstance(self.select_speaker_auto_verbose, bool): raise ValueError("select_speaker_auto_verbose cannot be None or non-bool") @@ -655,6 +678,10 @@ def validate_speaker_name(recipient, messages, sender, config) -> Tuple[bool, Un else: start_message = messages[-1] + # Add the message transforms, if any, to the speaker selection agent + if self._speaker_selection_transforms is not None: + self._speaker_selection_transforms.add_to_agent(speaker_selection_agent) + # Run the speaker selection chat result = checking_agent.initiate_chat( speaker_selection_agent, @@ -749,6 +776,10 @@ def validate_speaker_name(recipient, messages, sender, config) -> Tuple[bool, Un else: start_message = messages[-1] + # Add the message transforms, if any, to the speaker selection agent + if self._speaker_selection_transforms is not None: + self._speaker_selection_transforms.add_to_agent(speaker_selection_agent) + # Run the speaker selection chat result = await checking_agent.a_initiate_chat( speaker_selection_agent, diff --git a/test/agentchat/test_groupchat.py b/test/agentchat/test_groupchat.py index 291a94d450f1..39e8fb063026 100755 --- a/test/agentchat/test_groupchat.py +++ b/test/agentchat/test_groupchat.py @@ -12,6 +12,7 @@ import autogen from autogen import Agent, AssistantAgent, GroupChat, GroupChatManager +from autogen.agentchat.contrib.capabilities import transform_messages, transforms from autogen.exception_utils import AgentNameConflict, UndefinedNextAgent @@ -2061,6 +2062,46 @@ def test_manager_resume_messages(): return_agent, return_message = manager.resume(messages="Let's get this conversation started.") +def test_select_speaker_transform_messages(): + """Tests adding transform messages to a GroupChat for speaker selection when in 'auto' mode""" + + # Test adding a TransformMessages to a group chat + test_add_transforms = transform_messages.TransformMessages( + transforms=[ + transforms.MessageHistoryLimiter(max_messages=10), + transforms.MessageTokenLimiter(max_tokens=3000, max_tokens_per_message=500, min_tokens=300), + ] + ) + + coder = AssistantAgent(name="Coder", llm_config=None) + groupchat = GroupChat(messages=[], agents=[coder], select_speaker_transform_messages=test_add_transforms) + + # Ensure the transform have been added to the GroupChat + assert groupchat._speaker_selection_transforms == test_add_transforms + + # Attempt to add a non MessageTransforms object, such as a list of transforms + with pytest.raises(ValueError, match="select_speaker_transform_messages must be None or MessageTransforms."): + groupchat = GroupChat( + messages=[], + agents=[coder], + select_speaker_transform_messages=[transforms.MessageHistoryLimiter(max_messages=10)], + ) + + # Ensure if we don't pass any transforms in, none are on the GroupChat + groupchat_missing = GroupChat(messages=[], agents=[coder]) + + assert groupchat_missing._speaker_selection_transforms is None + + # Ensure we can pass in None + groupchat_none = GroupChat( + messages=[], + agents=[coder], + select_speaker_transform_messages=None, + ) + + assert groupchat_none._speaker_selection_transforms is None + + if __name__ == "__main__": # test_func_call_groupchat() # test_broadcast() @@ -2089,4 +2130,5 @@ def test_manager_resume_messages(): test_manager_resume_functions() # test_manager_resume_returns() # test_manager_resume_messages() + # test_select_speaker_transform_messages() pass diff --git a/website/docs/topics/groupchat/transform_messages_speaker_selection.ipynb b/website/docs/topics/groupchat/transform_messages_speaker_selection.ipynb new file mode 100644 index 000000000000..6e17d0cb94b3 --- /dev/null +++ b/website/docs/topics/groupchat/transform_messages_speaker_selection.ipynb @@ -0,0 +1,246 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Using Transform Messages during Speaker Selection\n", + "\n", + "When using \"auto\" mode for speaker selection in group chats, a nested-chat is used to determine the next speaker. This nested-chat includes all of the group chat's messages and this can result in a lot of content which the LLM needs to process for determining the next speaker. As conversations progress, it can be challenging to keep the context length within the workable window for the LLM. Furthermore, reducing the number of overall tokens will improve inference time and reduce token costs.\n", + "\n", + "Using [Transform Messages](/docs/topics/handling_long_contexts/intro_to_transform_messages) you gain control over which messages are used for speaker selection and the context length within each message as well as overall.\n", + "\n", + "All the transforms available for Transform Messages can be applied to the speaker selection nested-chat, such as the `MessageHistoryLimiter`, `MessageTokenLimiter`, and `TextMessageCompressor`.\n", + "\n", + "## How do I apply them\n", + "\n", + "When instantiating your `GroupChat` object, all you need to do is assign a [TransformMessages](/docs/reference/agentchat/contrib/capabilities/transform_messages#transformmessages) object to the `select_speaker_transform_messages` parameter, and the transforms within it will be applied to the nested speaker selection chats.\n", + "\n", + "And, as you're passing in a `TransformMessages` object, multiple transforms can be applied to that nested chat.\n", + "\n", + "As part of the nested-chat, an agent called 'checking_agent' is used to direct the LLM on selecting the next speaker. It is preferable to avoid compressing or truncating the content from this agent. How this is done is shown in the second last example." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Creating transforms for speaker selection in a GroupChat\n", + "\n", + "We will progressively create a `TransformMessage` object to show how you can build up transforms for speaker selection.\n", + "\n", + "Each iteration will replace the previous one, enabling you to use the code in each cell as is.\n", + "\n", + "Importantly, transforms are applied in the order that they are in the transforms list." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "outputs": [], + "source": [ + "# Start by importing the transform capabilities\n", + "\n", + "import autogen\n", + "from autogen.agentchat.contrib.capabilities import transform_messages, transforms" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "outputs": [], + "source": [ + "# Limit the number of messages\n", + "\n", + "# Let's start by limiting the number of messages to consider for speaker selection using a\n", + "# MessageHistoryLimiter transform. This example will use the latest 10 messages.\n", + "\n", + "select_speaker_transforms = transform_messages.TransformMessages(\n", + " transforms=[\n", + " transforms.MessageHistoryLimiter(max_messages=10),\n", + " ]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "outputs": [], + "source": [ + "# Compress messages through an LLM\n", + "\n", + "# An interesting and very powerful method of reducing tokens is by \"compressing\" the text of\n", + "# a message by using an LLM that's specifically designed to do that. The default LLM used for\n", + "# this purpose is LLMLingua (https://github.com/microsoft/LLMLingua) and it aims to reduce the\n", + "# number of tokens without reducing the message's meaning. We use the TextMessageCompressor\n", + "# transform to compress messages.\n", + "\n", + "# There are multiple LLMLingua models available and it defaults to the first version, LLMLingua.\n", + "# This example will show how to use LongLLMLingua which is targeted towards long-context\n", + "# information processing. LLMLingua-2 has been released and you could use that as well.\n", + "\n", + "# Create the compression arguments, which allow us to specify the model and other related\n", + "# parameters, such as whether to use the CPU or GPU.\n", + "select_speaker_compression_args = dict(\n", + " model_name=\"microsoft/llmlingua-2-xlm-roberta-large-meetingbank\", use_llmlingua2=True, device_map=\"cpu\"\n", + ")\n", + "\n", + "# Now we can add the TextMessageCompressor as the second step\n", + "\n", + "# Important notes on the parameters used:\n", + "# min_tokens - will only apply text compression if the message has at least 1,000 tokens\n", + "# cache - enables caching, if a message has previously been compressed it will use the\n", + "# cached version instead of recompressing it (making it much faster)\n", + "# filter_dict - to minimise the chance of compressing key information, we can include or\n", + "# exclude messages based on role and name.\n", + "# Here, we are excluding any 'system' messages as well as any messages from\n", + "# 'ceo' (just for example) and the 'checking_agent', which is an agent in the\n", + "# nested chat speaker selection chat. Change the 'ceo' name or add additional\n", + "# agent names for any agents that have critical content.\n", + "# exclude_filter - As we are setting this to True, the filter will be an exclusion filter.\n", + "\n", + "# Import the cache functionality\n", + "from autogen.cache.in_memory_cache import InMemoryCache\n", + "\n", + "select_speaker_transforms = transform_messages.TransformMessages(\n", + " transforms=[\n", + " transforms.MessageHistoryLimiter(max_messages=10),\n", + " transforms.TextMessageCompressor(\n", + " min_tokens=1000,\n", + " text_compressor=transforms.LLMLingua(select_speaker_compression_args, structured_compression=True),\n", + " cache=InMemoryCache(seed=43),\n", + " filter_dict={\"role\": [\"system\"], \"name\": [\"ceo\", \"checking_agent\"]},\n", + " exclude_filter=True,\n", + " ),\n", + " ]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "outputs": [], + "source": [ + "# Limit the total number of tokens and tokens per message\n", + "\n", + "# As a final example, we can manage the total tokens and individual message tokens. We have added a\n", + "# MessageTokenLimiter transform that will limit the total number of tokens for the messages to\n", + "# 3,000 with a maximum of 500 per individual message. Additionally, if a message is less than 300\n", + "# tokens it will not be truncated.\n", + "\n", + "select_speaker_compression_args = dict(\n", + " model_name=\"microsoft/llmlingua-2-xlm-roberta-large-meetingbank\", use_llmlingua2=True, device_map=\"cpu\"\n", + ")\n", + "\n", + "select_speaker_transforms = transform_messages.TransformMessages(\n", + " transforms=[\n", + " transforms.MessageHistoryLimiter(max_messages=10),\n", + " transforms.TextMessageCompressor(\n", + " min_tokens=1000,\n", + " text_compressor=transforms.LLMLingua(select_speaker_compression_args, structured_compression=True),\n", + " cache=InMemoryCache(seed=43),\n", + " filter_dict={\"role\": [\"system\"], \"name\": [\"ceo\", \"checking_agent\"]},\n", + " exclude_filter=True,\n", + " ),\n", + " transforms.MessageTokenLimiter(max_tokens=3000, max_tokens_per_message=500, min_tokens=300),\n", + " ]\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "plaintext" + } + }, + "outputs": [], + "source": [ + "# Now, we apply the transforms to a group chat. We do this by assigning the message\n", + "# transforms from above to the `select_speaker_transform_messages` parameter on the GroupChat.\n", + "\n", + "import os\n", + "\n", + "llm_config = {\n", + " \"config_list\": [{\"model\": \"gpt-4\", \"api_key\": os.environ[\"OPENAI_API_KEY\"]}],\n", + "}\n", + "\n", + "# Define your agents\n", + "chief_executive_officer = autogen.ConversableAgent(\n", + " \"ceo\",\n", + " llm_config=llm_config,\n", + " max_consecutive_auto_reply=1,\n", + " system_message=\"You are leading this group chat, and the business, as the chief executive officer.\",\n", + ")\n", + "\n", + "general_manager = autogen.ConversableAgent(\n", + " \"gm\",\n", + " llm_config=llm_config,\n", + " max_consecutive_auto_reply=1,\n", + " system_message=\"You are the general manager of the business, running the day-to-day operations.\",\n", + ")\n", + "\n", + "financial_controller = autogen.ConversableAgent(\n", + " \"fin_controller\",\n", + " llm_config=llm_config,\n", + " max_consecutive_auto_reply=1,\n", + " system_message=\"You are the financial controller, ensuring all financial matters are managed accordingly.\",\n", + ")\n", + "\n", + "your_group_chat = autogen.GroupChat(\n", + " agents=[chief_executive_officer, general_manager, financial_controller],\n", + " select_speaker_transform_messages=select_speaker_transforms,\n", + ")" + ] + } + ], + "metadata": { + "front_matter": { + "description": "Custom Speaker Selection Function", + "tags": [ + "orchestration", + "group chat" + ] + }, + "kernelspec": { + "display_name": "autogen", + "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.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From 4c147435db17c805262f9f48ff772a903501d777 Mon Sep 17 00:00:00 2001 From: Mark Sze <66362098+marklysze@users.noreply.github.com> Date: Mon, 26 Aug 2024 15:37:33 +1000 Subject: [PATCH 10/16] Fix for group chat resume - full chat history for each agent (#3412) --- autogen/agentchat/groupchat.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/autogen/agentchat/groupchat.py b/autogen/agentchat/groupchat.py index 232503ad5684..817d6d297b2e 100644 --- a/autogen/agentchat/groupchat.py +++ b/autogen/agentchat/groupchat.py @@ -1295,11 +1295,10 @@ def resume( if not message_speaker_agent and message["name"] == self.name: message_speaker_agent = self - # Add previous messages to each agent (except their own messages and the last message, as we'll kick off the conversation with it) + # Add previous messages to each agent (except the last message, as we'll kick off the conversation with it) if i != len(messages) - 1: for agent in self._groupchat.agents: - if agent.name != message["name"]: - self.send(message, self._groupchat.agent_by_name(agent.name), request_reply=False, silent=True) + self.send(message, self._groupchat.agent_by_name(agent.name), request_reply=False, silent=True) # Add previous message to the new groupchat, if it's an admin message the name may not match so add the message directly if message_speaker_agent: From 0b361dc37cfebbe6a3f02c192bc685b808b7013f Mon Sep 17 00:00:00 2001 From: New-World-2019 <37373361+New-World-2019@users.noreply.github.com> Date: Mon, 26 Aug 2024 14:54:46 +0800 Subject: [PATCH 11/16] =?UTF-8?q?Update=20agent=5Fchat.md=EF=BC=9BFix=20br?= =?UTF-8?q?oken=20URL=20(#3416)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This pull request includes a minor update to the agent_chat.md file to correct the link to the Enhanced Inference. Co-authored-by: gagb --- website/docs/Use-Cases/agent_chat.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/Use-Cases/agent_chat.md b/website/docs/Use-Cases/agent_chat.md index 46b555b3d7ca..76f9959841b8 100644 --- a/website/docs/Use-Cases/agent_chat.md +++ b/website/docs/Use-Cases/agent_chat.md @@ -21,7 +21,7 @@ The figure below shows the built-in agents in AutoGen. We have designed a generic [`ConversableAgent`](../reference/agentchat/conversable_agent.md#conversableagent-objects) class for Agents that are capable of conversing with each other through the exchange of messages to jointly finish a task. An agent can communicate with other agents and perform actions. Different agents can differ in what actions they perform after receiving messages. Two representative subclasses are [`AssistantAgent`](../reference/agentchat/assistant_agent.md#assistantagent-objects) and [`UserProxyAgent`](../reference/agentchat/user_proxy_agent.md#userproxyagent-objects) -- The [`AssistantAgent`](../reference/agentchat/assistant_agent.md#assistantagent-objects) is designed to act as an AI assistant, using LLMs by default but not requiring human input or code execution. It could write Python code (in a Python coding block) for a user to execute when a message (typically a description of a task that needs to be solved) is received. Under the hood, the Python code is written by LLM (e.g., GPT-4). It can also receive the execution results and suggest corrections or bug fixes. Its behavior can be altered by passing a new system message. The LLM [inference](#enhanced-inference) configuration can be configured via [`llm_config`]. +- The [`AssistantAgent`](../reference/agentchat/assistant_agent.md#assistantagent-objects) is designed to act as an AI assistant, using LLMs by default but not requiring human input or code execution. It could write Python code (in a Python coding block) for a user to execute when a message (typically a description of a task that needs to be solved) is received. Under the hood, the Python code is written by LLM (e.g., GPT-4). It can also receive the execution results and suggest corrections or bug fixes. Its behavior can be altered by passing a new system message. The LLM [inference](/docs/Use-Cases/enhanced_inference) configuration can be configured via [`llm_config`]. - The [`UserProxyAgent`](../reference/agentchat/user_proxy_agent.md#userproxyagent-objects) is conceptually a proxy agent for humans, soliciting human input as the agent's reply at each interaction turn by default and also having the capability to execute code and call functions or tools. The [`UserProxyAgent`](../reference/agentchat/user_proxy_agent.md#userproxyagent-objects) triggers code execution automatically when it detects an executable code block in the received message and no human user input is provided. Code execution can be disabled by setting the `code_execution_config` parameter to False. LLM-based response is disabled by default. It can be enabled by setting `llm_config` to a dict corresponding to the [inference](/docs/Use-Cases/enhanced_inference) configuration. When `llm_config` is set as a dictionary, [`UserProxyAgent`](../reference/agentchat/user_proxy_agent.md#userproxyagent-objects) can generate replies using an LLM when code execution is not performed. From 30b79ae071bc4a34770c66c3ce64cf5edc094a73 Mon Sep 17 00:00:00 2001 From: Aamir <48929123+heyitsaamir@users.noreply.github.com> Date: Mon, 26 Aug 2024 00:07:51 -0700 Subject: [PATCH 12/16] Add None back to remove_termination_string (#3410) Co-authored-by: Li Jiang --- autogen/agentchat/groupchat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autogen/agentchat/groupchat.py b/autogen/agentchat/groupchat.py index 817d6d297b2e..2ebdf95b7d37 100644 --- a/autogen/agentchat/groupchat.py +++ b/autogen/agentchat/groupchat.py @@ -1340,7 +1340,7 @@ def resume( async def a_resume( self, messages: Union[List[Dict], str], - remove_termination_string: Union[str, Callable[[str], str]], + remove_termination_string: Union[str, Callable[[str], str]] = None, silent: Optional[bool] = False, ) -> Tuple[ConversableAgent, Dict]: """Resumes a group chat using the previous messages as a starting point, asynchronously. Requires the agents, group chat, and group chat manager to be established From 34b34d02034848e83427441b3ecc51b7666cefc2 Mon Sep 17 00:00:00 2001 From: HRUSHIKESH DOKALA <96101829+Hk669@users.noreply.github.com> Date: Mon, 26 Aug 2024 12:58:53 +0530 Subject: [PATCH 13/16] Amazon Bedrock Client for AutoGen (#3232) * intial commit for aws-bedrock * format * converse setup for model req-response * Renamed to bedrock.py, updated parameter parsing, system message extraction, client class incorporation * Established Bedrock class based on @astroalek and @ChristianT's code, added ability to disable system prompt separation * Image parsing and removing access credential checks * Added tests, added additional parameter support * Amazon Bedrock documentation * Moved client parameters to init, align parameter names with Anthropic, spelling fix, remove unnecessary imports, use base and additional parameters, update documentation * Tidy up comments * Minor typo fix * Correct comment re aws_region --------- Co-authored-by: Mark Sze Co-authored-by: Mark Sze <66362098+marklysze@users.noreply.github.com> Co-authored-by: Li Jiang --- .github/workflows/contrib-tests.yml | 40 + autogen/logger/file_logger.py | 2 + autogen/logger/sqlite_logger.py | 2 + autogen/oai/bedrock.py | 606 ++++++++ autogen/oai/client.py | 17 +- autogen/runtime_logging.py | 11 +- setup.py | 1 + test/oai/test_bedrock.py | 294 ++++ .../non-openai-models/cloud-anthropic.ipynb | 3 +- .../non-openai-models/cloud-bedrock.ipynb | 1298 +++++++++++++++++ .../non-openai-models/cloud-cohere.ipynb | 2 +- 11 files changed, 2270 insertions(+), 6 deletions(-) create mode 100644 autogen/oai/bedrock.py create mode 100644 test/oai/test_bedrock.py create mode 100644 website/docs/topics/non-openai-models/cloud-bedrock.ipynb diff --git a/.github/workflows/contrib-tests.yml b/.github/workflows/contrib-tests.yml index 7aad6ebbf067..3abe257dfad6 100644 --- a/.github/workflows/contrib-tests.yml +++ b/.github/workflows/contrib-tests.yml @@ -612,3 +612,43 @@ jobs: with: file: ./coverage.xml flags: unittests + + BedrockTest: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-2019] + python-version: ["3.9", "3.10", "3.11", "3.12"] + exclude: + - os: macos-latest + python-version: "3.9" + steps: + - uses: actions/checkout@v4 + with: + lfs: true + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + - name: Install packages and dependencies for all tests + run: | + python -m pip install --upgrade pip wheel + pip install pytest-cov>=5 + - name: Install packages and dependencies for Amazon Bedrock + run: | + pip install -e .[boto3,test] + - name: Set AUTOGEN_USE_DOCKER based on OS + shell: bash + run: | + if [[ ${{ matrix.os }} != ubuntu-latest ]]; then + echo "AUTOGEN_USE_DOCKER=False" >> $GITHUB_ENV + fi + - name: Coverage + run: | + pytest test/oai/test_bedrock.py --skip-openai + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 + with: + file: ./coverage.xml + flags: unittests diff --git a/autogen/logger/file_logger.py b/autogen/logger/file_logger.py index 61a8a6335284..37bbbd25a523 100644 --- a/autogen/logger/file_logger.py +++ b/autogen/logger/file_logger.py @@ -18,6 +18,7 @@ if TYPE_CHECKING: from autogen import Agent, ConversableAgent, OpenAIWrapper from autogen.oai.anthropic import AnthropicClient + from autogen.oai.bedrock import BedrockClient from autogen.oai.cohere import CohereClient from autogen.oai.gemini import GeminiClient from autogen.oai.groq import GroqClient @@ -215,6 +216,7 @@ def log_new_client( | TogetherClient | GroqClient | CohereClient + | BedrockClient ), wrapper: OpenAIWrapper, init_args: Dict[str, Any], diff --git a/autogen/logger/sqlite_logger.py b/autogen/logger/sqlite_logger.py index 2cf176ebb8f2..f76d039ce9de 100644 --- a/autogen/logger/sqlite_logger.py +++ b/autogen/logger/sqlite_logger.py @@ -19,6 +19,7 @@ if TYPE_CHECKING: from autogen import Agent, ConversableAgent, OpenAIWrapper from autogen.oai.anthropic import AnthropicClient + from autogen.oai.bedrock import BedrockClient from autogen.oai.cohere import CohereClient from autogen.oai.gemini import GeminiClient from autogen.oai.groq import GroqClient @@ -402,6 +403,7 @@ def log_new_client( TogetherClient, GroqClient, CohereClient, + BedrockClient, ], wrapper: OpenAIWrapper, init_args: Dict[str, Any], diff --git a/autogen/oai/bedrock.py b/autogen/oai/bedrock.py new file mode 100644 index 000000000000..7894781e3ee5 --- /dev/null +++ b/autogen/oai/bedrock.py @@ -0,0 +1,606 @@ +""" +Create a compatible client for the Amazon Bedrock Converse API. + +Example usage: +Install the `boto3` package by running `pip install --upgrade boto3`. +- https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html + +import autogen + +config_list = [ + { + "api_type": "bedrock", + "model": "meta.llama3-1-8b-instruct-v1:0", + "aws_region": "us-west-2", + "aws_access_key": "", + "aws_secret_key": "", + "price" : [0.003, 0.015] + } +] + +assistant = autogen.AssistantAgent("assistant", llm_config={"config_list": config_list}) + +""" + +from __future__ import annotations + +import base64 +import json +import os +import re +import time +import warnings +from typing import Any, Dict, List, Literal, Tuple + +import boto3 +import requests +from botocore.config import Config +from openai.types.chat import ChatCompletion, ChatCompletionMessageToolCall +from openai.types.chat.chat_completion import ChatCompletionMessage, Choice +from openai.types.completion_usage import CompletionUsage + +from autogen.oai.client_utils import validate_parameter + + +class BedrockClient: + """Client for Amazon's Bedrock Converse API.""" + + _retries = 5 + + def __init__(self, **kwargs: Any): + """ + Initialises BedrockClient for Amazon's Bedrock Converse API + """ + self._aws_access_key = kwargs.get("aws_access_key", None) + self._aws_secret_key = kwargs.get("aws_secret_key", None) + self._aws_session_token = kwargs.get("aws_session_token", None) + self._aws_region = kwargs.get("aws_region", None) + self._aws_profile_name = kwargs.get("aws_profile_name", None) + + if not self._aws_access_key: + self._aws_access_key = os.getenv("AWS_ACCESS_KEY") + + if not self._aws_secret_key: + self._aws_secret_key = os.getenv("AWS_SECRET_KEY") + + if not self._aws_session_token: + self._aws_session_token = os.getenv("AWS_SESSION_TOKEN") + + if not self._aws_region: + self._aws_region = os.getenv("AWS_REGION") + + if self._aws_region is None: + raise ValueError("Region is required to use the Amazon Bedrock API.") + + # Initialize Bedrock client, session, and runtime + bedrock_config = Config( + region_name=self._aws_region, + signature_version="v4", + retries={"max_attempts": self._retries, "mode": "standard"}, + ) + + session = boto3.Session( + aws_access_key_id=self._aws_access_key, + aws_secret_access_key=self._aws_secret_key, + aws_session_token=self._aws_session_token, + profile_name=self._aws_profile_name, + ) + + self.bedrock_runtime = session.client(service_name="bedrock-runtime", config=bedrock_config) + + def message_retrieval(self, response): + """Retrieve the messages from the response.""" + return [choice.message for choice in response.choices] + + def parse_custom_params(self, params: Dict[str, Any]): + """ + Parses custom parameters for logic in this client class + """ + + # Should we separate system messages into its own request parameter, default is True + # This is required because not all models support a system prompt (e.g. Mistral Instruct). + self._supports_system_prompts = params.get("supports_system_prompts", True) + + def parse_params(self, params: Dict[str, Any]) -> tuple[Dict[str, Any], Dict[str, Any]]: + """ + Loads the valid parameters required to invoke Bedrock Converse + Returns a tuple of (base_params, additional_params) + """ + + base_params = {} + additional_params = {} + + # Amazon Bedrock base model IDs are here: + # https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids.html + self._model_id = params.get("model", None) + assert self._model_id, "Please provide the 'model` in the config_list to use Amazon Bedrock" + + # Parameters vary based on the model used. + # As we won't cater for all models and parameters, it's the developer's + # responsibility to implement the parameters and they will only be + # included if the developer has it in the config. + # + # Important: + # No defaults will be used (as they can vary per model) + # No ranges will be used (as they can vary) + # We will cover all the main parameters but there may be others + # that need to be added later + # + # Here are some pages that show the parameters available for different models + # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-titan-text.html + # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-text-completion.html + # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere-command-r-plus.html + # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-meta.html + # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-mistral-chat-completion.html + + # Here are the possible "base" parameters and their suitable types + base_parameters = [["temperature", (float, int)], ["topP", (float, int)], ["maxTokens", (int)]] + + for param_name, suitable_types in base_parameters: + if param_name in params: + base_params[param_name] = validate_parameter( + params, param_name, suitable_types, False, None, None, None + ) + + # Here are the possible "model-specific" parameters and their suitable types, known as additional parameters + additional_parameters = [ + ["top_p", (float, int)], + ["top_k", (int)], + ["k", (int)], + ["seed", (int)], + ] + + for param_name, suitable_types in additional_parameters: + if param_name in params: + additional_params[param_name] = validate_parameter( + params, param_name, suitable_types, False, None, None, None + ) + + # Streaming + if "stream" in params: + self._streaming = params["stream"] + else: + self._streaming = False + + # For this release we will not support streaming as many models do not support streaming with tool use + if self._streaming: + warnings.warn( + "Streaming is not currently supported, streaming will be disabled.", + UserWarning, + ) + self._streaming = False + + return base_params, additional_params + + def create(self, params): + """Run Amazon Bedrock inference and return AutoGen response""" + + # Set custom client class settings + self.parse_custom_params(params) + + # Parse the inference parameters + base_params, additional_params = self.parse_params(params) + + has_tools = "tools" in params + messages = oai_messages_to_bedrock_messages(params["messages"], has_tools, self._supports_system_prompts) + + if self._supports_system_prompts: + system_messages = extract_system_messages(params["messages"]) + + tool_config = format_tools(params["tools"] if has_tools else []) + + request_args = {"messages": messages, "modelId": self._model_id} + + # Base and additional args + if len(base_params) > 0: + request_args["inferenceConfig"] = base_params + + if len(additional_params) > 0: + request_args["additionalModelRequestFields"] = additional_params + + if self._supports_system_prompts: + request_args["system"] = system_messages + + if len(tool_config["tools"]) > 0: + request_args["toolConfig"] = tool_config + + try: + response = self.bedrock_runtime.converse( + **request_args, + ) + except Exception as e: + raise RuntimeError(f"Failed to get response from Bedrock: {e}") + + if response is None: + raise RuntimeError(f"Failed to get response from Bedrock after retrying {self._retries} times.") + + finish_reason = convert_stop_reason_to_finish_reason(response["stopReason"]) + response_message = response["output"]["message"] + + if finish_reason == "tool_calls": + tool_calls = format_tool_calls(response_message["content"]) + # text = "" + else: + tool_calls = None + + text = "" + for content in response_message["content"]: + if "text" in content: + text = content["text"] + # NOTE: other types of output may be dealt with here + + message = ChatCompletionMessage(role="assistant", content=text, tool_calls=tool_calls) + + response_usage = response["usage"] + usage = CompletionUsage( + prompt_tokens=response_usage["inputTokens"], + completion_tokens=response_usage["outputTokens"], + total_tokens=response_usage["totalTokens"], + ) + + return ChatCompletion( + id=response["ResponseMetadata"]["RequestId"], + choices=[Choice(finish_reason=finish_reason, index=0, message=message)], + created=int(time.time()), + model=self._model_id, + object="chat.completion", + usage=usage, + ) + + def cost(self, response: ChatCompletion) -> float: + """Calculate the cost of the response.""" + return calculate_cost(response.usage.prompt_tokens, response.usage.completion_tokens, response.model) + + @staticmethod + def get_usage(response) -> Dict: + """Get the usage of tokens and their cost information.""" + return { + "prompt_tokens": response.usage.prompt_tokens, + "completion_tokens": response.usage.completion_tokens, + "total_tokens": response.usage.total_tokens, + "cost": response.cost, + "model": response.model, + } + + +def extract_system_messages(messages: List[dict]) -> List: + """Extract the system messages from the list of messages. + + Args: + messages (list[dict]): List of messages. + + Returns: + List[SystemMessage]: List of System messages. + """ + + """ + system_messages = [message.get("content")[0]["text"] for message in messages if message.get("role") == "system"] + return system_messages # ''.join(system_messages) + """ + + for message in messages: + if message.get("role") == "system": + if isinstance(message["content"], str): + return [{"text": message.get("content")}] + else: + return [{"text": message.get("content")[0]["text"]}] + return [] + + +def oai_messages_to_bedrock_messages( + messages: List[Dict[str, Any]], has_tools: bool, supports_system_prompts: bool +) -> List[Dict]: + """ + Convert messages from OAI format to Bedrock format. + We correct for any specific role orders and types, etc. + AWS Bedrock requires messages to alternate between user and assistant roles. This function ensures that the messages + are in the correct order and format for Bedrock by inserting "Please continue" messages as needed. + This is the same method as the one in the Autogen Anthropic client + """ + + # Track whether we have tools passed in. If not, tool use / result messages should be converted to text messages. + # Bedrock requires a tools parameter with the tools listed, if there are other messages with tool use or tool results. + # This can occur when we don't need tool calling, such as for group chat speaker selection + + # Convert messages to Bedrock compliant format + + # Take out system messages if the model supports it, otherwise leave them in. + if supports_system_prompts: + messages = [x for x in messages if not x["role"] == "system"] + else: + # Replace role="system" with role="user" + for msg in messages: + if msg["role"] == "system": + msg["role"] = "user" + + processed_messages = [] + + # Used to interweave user messages to ensure user/assistant alternating + user_continue_message = {"content": [{"text": "Please continue."}], "role": "user"} + assistant_continue_message = { + "content": [{"text": "Please continue."}], + "role": "assistant", + } + + tool_use_messages = 0 + tool_result_messages = 0 + last_tool_use_index = -1 + last_tool_result_index = -1 + # user_role_index = 0 if supports_system_prompts else 1 # If system prompts are supported, messages start with user, otherwise they'll be the second message + for message in messages: + # New messages will be added here, manage role alternations + expected_role = "user" if len(processed_messages) % 2 == 0 else "assistant" + + if "tool_calls" in message: + # Map the tool call options to Bedrock's format + tool_uses = [] + tool_names = [] + for tool_call in message["tool_calls"]: + tool_uses.append( + { + "toolUse": { + "toolUseId": tool_call["id"], + "name": tool_call["function"]["name"], + "input": json.loads(tool_call["function"]["arguments"]), + } + } + ) + if has_tools: + tool_use_messages += 1 + tool_names.append(tool_call["function"]["name"]) + + if expected_role == "user": + # Insert an extra user message as we will append an assistant message + processed_messages.append(user_continue_message) + + if has_tools: + processed_messages.append({"role": "assistant", "content": tool_uses}) + last_tool_use_index = len(processed_messages) - 1 + else: + # Not using tools, so put in a plain text message + processed_messages.append( + { + "role": "assistant", + "content": [ + {"text": f"Some internal function(s) that could be used: [{', '.join(tool_names)}]"} + ], + } + ) + elif "tool_call_id" in message: + if has_tools: + # Map the tool usage call to tool_result for Bedrock + tool_result = { + "toolResult": { + "toolUseId": message["tool_call_id"], + "content": [{"text": message["content"]}], + } + } + + # If the previous message also had a tool_result, add it to that + # Otherwise append a new message + if last_tool_result_index == len(processed_messages) - 1: + processed_messages[-1]["content"].append(tool_result) + else: + if expected_role == "assistant": + # Insert an extra assistant message as we will append a user message + processed_messages.append(assistant_continue_message) + + processed_messages.append({"role": "user", "content": [tool_result]}) + last_tool_result_index = len(processed_messages) - 1 + + tool_result_messages += 1 + else: + # Not using tools, so put in a plain text message + processed_messages.append( + { + "role": "user", + "content": [{"text": f"Running the function returned: {message['content']}"}], + } + ) + elif message["content"] == "": + # Ignoring empty messages + pass + else: + if expected_role != message["role"] and not (len(processed_messages) == 0 and message["role"] == "system"): + # Inserting the alternating continue message (ignore if it's the first message and a system message) + processed_messages.append( + user_continue_message if expected_role == "user" else assistant_continue_message + ) + + processed_messages.append( + { + "role": message["role"], + "content": parse_content_parts(message=message), + } + ) + + # We'll replace the last tool_use if there's no tool_result (occurs if we finish the conversation before running the function) + if has_tools and tool_use_messages != tool_result_messages: + processed_messages[last_tool_use_index] = assistant_continue_message + + # name is not a valid field on messages + for message in processed_messages: + if "name" in message: + message.pop("name", None) + + # Note: When using reflection_with_llm we may end up with an "assistant" message as the last message and that may cause a blank response + # So, if the last role is not user, add a 'user' continue message at the end + if processed_messages[-1]["role"] != "user": + processed_messages.append(user_continue_message) + + return processed_messages + + +def parse_content_parts( + message: Dict[str, Any], +) -> List[dict]: + content: str | List[Dict[str, Any]] = message.get("content") + if isinstance(content, str): + return [ + { + "text": content, + } + ] + content_parts = [] + for part in content: + # part_content: Dict = part.get("content") + if "text" in part: # part_content: + content_parts.append( + { + "text": part.get("text"), + } + ) + elif "image_url" in part: # part_content: + image_data, content_type = parse_image(part.get("image_url").get("url")) + content_parts.append( + { + "image": { + "format": content_type[6:], # image/ + "source": {"bytes": image_data}, + }, + } + ) + else: + # Ignore.. + continue + return content_parts + + +def parse_image(image_url: str) -> Tuple[bytes, str]: + """Try to get the raw data from an image url. + + Ref: https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ImageSource.html + returns a tuple of (Image Data, Content Type) + """ + pattern = r"^data:(image/[a-z]*);base64,\s*" + content_type = re.search(pattern, image_url) + # if already base64 encoded. + # Only supports 'image/jpeg', 'image/png', 'image/gif' or 'image/webp' + if content_type: + image_data = re.sub(pattern, "", image_url) + return base64.b64decode(image_data), content_type.group(1) + + # Send a request to the image URL + response = requests.get(image_url) + # Check if the request was successful + if response.status_code == 200: + + content_type = response.headers.get("Content-Type") + if not content_type.startswith("image"): + content_type = "image/jpeg" + # Get the image content + image_content = response.content + return image_content, content_type + else: + raise RuntimeError("Unable to access the image url") + + +def format_tools(tools: List[Dict[str, Any]]) -> Dict[Literal["tools"], List[Dict[str, Any]]]: + converted_schema = {"tools": []} + + for tool in tools: + if tool["type"] == "function": + function = tool["function"] + converted_tool = { + "toolSpec": { + "name": function["name"], + "description": function["description"], + "inputSchema": {"json": {"type": "object", "properties": {}, "required": []}}, + } + } + + for prop_name, prop_details in function["parameters"]["properties"].items(): + converted_tool["toolSpec"]["inputSchema"]["json"]["properties"][prop_name] = { + "type": prop_details["type"], + "description": prop_details.get("description", ""), + } + if "enum" in prop_details: + converted_tool["toolSpec"]["inputSchema"]["json"]["properties"][prop_name]["enum"] = prop_details[ + "enum" + ] + if "default" in prop_details: + converted_tool["toolSpec"]["inputSchema"]["json"]["properties"][prop_name]["default"] = ( + prop_details["default"] + ) + + if "required" in function["parameters"]: + converted_tool["toolSpec"]["inputSchema"]["json"]["required"] = function["parameters"]["required"] + + converted_schema["tools"].append(converted_tool) + + return converted_schema + + +def format_tool_calls(content): + """Converts Converse API response tool calls to AutoGen format""" + tool_calls = [] + for tool_request in content: + if "toolUse" in tool_request: + tool = tool_request["toolUse"] + + tool_calls.append( + ChatCompletionMessageToolCall( + id=tool["toolUseId"], + function={ + "name": tool["name"], + "arguments": json.dumps(tool["input"]), + }, + type="function", + ) + ) + return tool_calls + + +def convert_stop_reason_to_finish_reason( + stop_reason: str, +) -> Literal["stop", "length", "tool_calls", "content_filter"]: + """ + Converts Bedrock finish reasons to our finish reasons, according to OpenAI: + + - stop: if the model hit a natural stop point or a provided stop sequence, + - length: if the maximum number of tokens specified in the request was reached, + - content_filter: if content was omitted due to a flag from our content filters, + - tool_calls: if the model called a tool + """ + if stop_reason: + finish_reason_mapping = { + "tool_use": "tool_calls", + "finished": "stop", + "end_turn": "stop", + "max_tokens": "length", + "stop_sequence": "stop", + "complete": "stop", + "content_filtered": "content_filter", + } + return finish_reason_mapping.get(stop_reason.lower(), stop_reason.lower()) + + warnings.warn(f"Unsupported stop reason: {stop_reason}", UserWarning) + return None + + +# NOTE: As this will be quite dynamic, it's expected that the developer will use the "price" parameter in their config +# These may be removed. +PRICES_PER_K_TOKENS = { + "meta.llama3-8b-instruct-v1:0": (0.0003, 0.0006), + "meta.llama3-70b-instruct-v1:0": (0.00265, 0.0035), + "mistral.mistral-7b-instruct-v0:2": (0.00015, 0.0002), + "mistral.mixtral-8x7b-instruct-v0:1": (0.00045, 0.0007), + "mistral.mistral-large-2402-v1:0": (0.004, 0.012), + "mistral.mistral-small-2402-v1:0": (0.001, 0.003), +} + + +def calculate_cost(input_tokens: int, output_tokens: int, model_id: str) -> float: + """Calculate the cost of the completion using the Bedrock pricing.""" + + if model_id in PRICES_PER_K_TOKENS: + input_cost_per_k, output_cost_per_k = PRICES_PER_K_TOKENS[model_id] + input_cost = (input_tokens / 1000) * input_cost_per_k + output_cost = (output_tokens / 1000) * output_cost_per_k + return input_cost + output_cost + else: + warnings.warn( + f'Cannot get the costs for {model_id}. The cost will be 0. In your config_list, add field {{"price" : [prompt_price_per_1k, completion_token_price_per_1k]}} for customized pricing.', + UserWarning, + ) + return 0 diff --git a/autogen/oai/client.py b/autogen/oai/client.py index fb13afdfcc63..3ae37257b21e 100644 --- a/autogen/oai/client.py +++ b/autogen/oai/client.py @@ -84,6 +84,13 @@ except ImportError as e: cohere_import_exception = e +try: + from autogen.oai.bedrock import BedrockClient + + bedrock_import_exception: Optional[ImportError] = None +except ImportError as e: + bedrock_import_exception = e + logger = logging.getLogger(__name__) if not logger.handlers: # Add the console handler. @@ -457,7 +464,7 @@ def _configure_azure_openai(self, config: Dict[str, Any], openai_config: Dict[st def _configure_openai_config_for_bedrock(self, config: Dict[str, Any], openai_config: Dict[str, Any]) -> None: """Update openai_config with AWS credentials from config.""" required_keys = ["aws_access_key", "aws_secret_key", "aws_region"] - optional_keys = ["aws_session_token"] + optional_keys = ["aws_session_token", "aws_profile_name"] for key in required_keys: if key in config: openai_config[key] = config[key] @@ -519,9 +526,15 @@ def _register_default_client(self, config: Dict[str, Any], openai_config: Dict[s self._clients.append(client) elif api_type is not None and api_type.startswith("cohere"): if cohere_import_exception: - raise ImportError("Please install `cohere` to use the Groq API.") + raise ImportError("Please install `cohere` to use the Cohere API.") client = CohereClient(**openai_config) self._clients.append(client) + elif api_type is not None and api_type.startswith("bedrock"): + self._configure_openai_config_for_bedrock(config, openai_config) + if bedrock_import_exception: + raise ImportError("Please install `boto3` to use the Amazon Bedrock API.") + client = BedrockClient(**openai_config) + self._clients.append(client) else: client = OpenAI(**openai_config) self._clients.append(OpenAIClient(client)) diff --git a/autogen/runtime_logging.py b/autogen/runtime_logging.py index 1ffc8b622f0a..0fd7cc2fc8b9 100644 --- a/autogen/runtime_logging.py +++ b/autogen/runtime_logging.py @@ -14,6 +14,7 @@ if TYPE_CHECKING: from autogen import Agent, ConversableAgent, OpenAIWrapper from autogen.oai.anthropic import AnthropicClient + from autogen.oai.bedrock import BedrockClient from autogen.oai.cohere import CohereClient from autogen.oai.gemini import GeminiClient from autogen.oai.groq import GroqClient @@ -113,7 +114,15 @@ def log_new_wrapper(wrapper: OpenAIWrapper, init_args: Dict[str, Union[LLMConfig def log_new_client( client: Union[ - AzureOpenAI, OpenAI, GeminiClient, AnthropicClient, MistralAIClient, TogetherClient, GroqClient, CohereClient + AzureOpenAI, + OpenAI, + GeminiClient, + AnthropicClient, + MistralAIClient, + TogetherClient, + GroqClient, + CohereClient, + BedrockClient, ], wrapper: OpenAIWrapper, init_args: Dict[str, Any], diff --git a/setup.py b/setup.py index 95b2cda212ae..bd75dab16b89 100644 --- a/setup.py +++ b/setup.py @@ -91,6 +91,7 @@ "mistral": ["mistralai>=1.0.1"], "groq": ["groq>=0.9.0"], "cohere": ["cohere>=5.5.8"], + "bedrock": ["boto3>=1.34.149"], } setuptools.setup( diff --git a/test/oai/test_bedrock.py b/test/oai/test_bedrock.py new file mode 100644 index 000000000000..42502acf691c --- /dev/null +++ b/test/oai/test_bedrock.py @@ -0,0 +1,294 @@ +from unittest.mock import MagicMock, patch + +import pytest + +try: + from autogen.oai.bedrock import BedrockClient, oai_messages_to_bedrock_messages + + skip = False +except ImportError: + BedrockClient = object + InternalServerError = object + skip = True + + +# Fixtures for mock data +@pytest.fixture +def mock_response(): + class MockResponse: + def __init__(self, text, choices, usage, cost, model): + self.text = text + self.choices = choices + self.usage = usage + self.cost = cost + self.model = model + + return MockResponse + + +@pytest.fixture +def bedrock_client(): + + # Set Bedrock client with some default values + client = BedrockClient() + + client._supports_system_prompts = True + + return client + + +skip_reason = "Amazon Bedrock dependency is not installed" + + +# Test initialization and configuration +@pytest.mark.skipif(skip, reason=skip_reason) +def test_initialization(): + + # Creation works without an api_key as it's handled in the parameter parsing + BedrockClient() + + +# Test parameters +@pytest.mark.skipif(skip, reason=skip_reason) +def test_parsing_params(bedrock_client): + # All parameters (with default values) + params = { + # "aws_region_name": "us-east-1", + # "aws_access_key_id": "test_access_key_id", + # "aws_secret_access_key": "test_secret_access_key", + # "aws_session_token": "test_session_token", + # "aws_profile_name": "test_profile_name", + "model": "anthropic.claude-3-sonnet-20240229-v1:0", + "temperature": 0.8, + "topP": 0.6, + "maxTokens": 250, + "seed": 42, + "stream": False, + } + expected_base_params = { + "temperature": 0.8, + "topP": 0.6, + "maxTokens": 250, + } + expected_additional_params = { + "seed": 42, + } + base_result, additional_result = bedrock_client.parse_params(params) + assert base_result == expected_base_params + assert additional_result == expected_additional_params + + # Incorrect types, defaults should be set, will show warnings but not trigger assertions + params = { + "model": "anthropic.claude-3-sonnet-20240229-v1:0", + "temperature": "0.5", + "topP": "0.6", + "maxTokens": "250", + "seed": "42", + "stream": "False", + } + expected_base_params = { + "temperature": None, + "topP": None, + "maxTokens": None, + } + expected_additional_params = { + "seed": None, + } + base_result, additional_result = bedrock_client.parse_params(params) + assert base_result == expected_base_params + assert additional_result == expected_additional_params + + # Only model, others set as defaults if they are mandatory + params = { + "model": "anthropic.claude-3-sonnet-20240229-v1:0", + } + expected_base_params = {} + expected_additional_params = {} + base_result, additional_result = bedrock_client.parse_params(params) + assert base_result == expected_base_params + assert additional_result == expected_additional_params + + # No model + params = { + "temperature": 0.8, + } + + with pytest.raises(AssertionError) as assertinfo: + bedrock_client.parse_params(params) + + assert "Please provide the 'model` in the config_list to use Amazon Bedrock" in str(assertinfo.value) + + +# Test text generation +@pytest.mark.skipif(skip, reason=skip_reason) +@patch("autogen.oai.bedrock.BedrockClient.create") +def test_create_response(mock_chat, bedrock_client): + # Mock BedrockClient.chat response + mock_bedrock_response = MagicMock() + mock_bedrock_response.choices = [ + MagicMock(finish_reason="stop", message=MagicMock(content="Example Bedrock response", tool_calls=None)) + ] + mock_bedrock_response.id = "mock_bedrock_response_id" + mock_bedrock_response.model = "anthropic.claude-3-sonnet-20240229-v1:0" + mock_bedrock_response.usage = MagicMock(prompt_tokens=10, completion_tokens=20) # Example token usage + + mock_chat.return_value = mock_bedrock_response + + # Test parameters + params = { + "messages": [{"role": "user", "content": "Hello"}, {"role": "assistant", "content": "World"}], + "model": "anthropic.claude-3-sonnet-20240229-v1:0", + } + + # Call the create method + response = bedrock_client.create(params) + + # Assertions to check if response is structured as expected + assert ( + response.choices[0].message.content == "Example Bedrock response" + ), "Response content should match expected output" + assert response.id == "mock_bedrock_response_id", "Response ID should match the mocked response ID" + assert ( + response.model == "anthropic.claude-3-sonnet-20240229-v1:0" + ), "Response model should match the mocked response model" + assert response.usage.prompt_tokens == 10, "Response prompt tokens should match the mocked response usage" + assert response.usage.completion_tokens == 20, "Response completion tokens should match the mocked response usage" + + +# Test functions/tools +@pytest.mark.skipif(skip, reason=skip_reason) +@patch("autogen.oai.bedrock.BedrockClient.create") +def test_create_response_with_tool_call(mock_chat, bedrock_client): + # Mock BedrockClient.chat response + mock_function = MagicMock(name="currency_calculator") + mock_function.name = "currency_calculator" + mock_function.arguments = '{"base_currency": "EUR", "quote_currency": "USD", "base_amount": 123.45}' + + mock_function_2 = MagicMock(name="get_weather") + mock_function_2.name = "get_weather" + mock_function_2.arguments = '{"location": "New York"}' + + mock_chat.return_value = MagicMock( + choices=[ + MagicMock( + finish_reason="tool_calls", + message=MagicMock( + content="Sample text about the functions", + tool_calls=[ + MagicMock(id="bd65600d-8669-4903-8a14-af88203add38", function=mock_function), + MagicMock(id="f50ec0b7-f960-400d-91f0-c42a6d44e3d0", function=mock_function_2), + ], + ), + ) + ], + id="mock_bedrock_response_id", + model="anthropic.claude-3-sonnet-20240229-v1:0", + usage=MagicMock(prompt_tokens=10, completion_tokens=20), + ) + + # Construct parameters + converted_functions = [ + { + "type": "function", + "function": { + "description": "Currency exchange calculator.", + "name": "currency_calculator", + "parameters": { + "type": "object", + "properties": { + "base_amount": {"type": "number", "description": "Amount of currency in base_currency"}, + }, + "required": ["base_amount"], + }, + }, + } + ] + bedrock_messages = [ + {"role": "user", "content": "How much is 123.45 EUR in USD?"}, + {"role": "assistant", "content": "World"}, + ] + + # Call the create method + response = bedrock_client.create( + {"messages": bedrock_messages, "tools": converted_functions, "model": "anthropic.claude-3-sonnet-20240229-v1:0"} + ) + + # Assertions to check if the functions and content are included in the response + assert response.choices[0].message.content == "Sample text about the functions" + assert response.choices[0].message.tool_calls[0].function.name == "currency_calculator" + assert response.choices[0].message.tool_calls[1].function.name == "get_weather" + + +# Test message conversion from OpenAI to Bedrock format +@pytest.mark.skipif(skip, reason=skip_reason) +def test_oai_messages_to_bedrock_messages(bedrock_client): + + # Test that the "name" key is removed and system messages converted to user message + test_messages = [ + {"role": "system", "content": "You are a helpful AI bot."}, + {"role": "user", "name": "anne", "content": "Why is the sky blue?"}, + ] + messages = oai_messages_to_bedrock_messages(test_messages, False, False) + + expected_messages = [ + {"role": "user", "content": [{"text": "You are a helpful AI bot."}]}, + {"role": "assistant", "content": [{"text": "Please continue."}]}, + {"role": "user", "content": [{"text": "Why is the sky blue?"}]}, + ] + + assert messages == expected_messages, "'name' was not removed from messages (system message should be user message)" + + # Test that the "name" key is removed and system messages are extracted (as they will be put in separately) + test_messages = [ + {"role": "system", "content": "You are a helpful AI bot."}, + {"role": "user", "name": "anne", "content": "Why is the sky blue?"}, + ] + messages = oai_messages_to_bedrock_messages(test_messages, False, True) + + expected_messages = [ + {"role": "user", "content": [{"text": "Why is the sky blue?"}]}, + ] + + assert messages == expected_messages, "'name' was not removed from messages (system messages excluded)" + + # Test that the system message is converted to user and that a continue message is inserted + test_messages = [ + {"role": "system", "content": "You are a helpful AI bot."}, + {"role": "user", "name": "anne", "content": "Why is the sky blue?"}, + {"role": "system", "content": "Summarise the conversation."}, + ] + + messages = oai_messages_to_bedrock_messages(test_messages, False, False) + + expected_messages = [ + {"role": "user", "content": [{"text": "You are a helpful AI bot."}]}, + {"role": "assistant", "content": [{"text": "Please continue."}]}, + {"role": "user", "content": [{"text": "Why is the sky blue?"}]}, + {"role": "assistant", "content": [{"text": "Please continue."}]}, + {"role": "user", "content": [{"text": "Summarise the conversation."}]}, + ] + + assert ( + messages == expected_messages + ), "Final 'system' message was not changed to 'user' or continue messages not included" + + # Test that the last message is a user or system message and if not, add a continue message + test_messages = [ + {"role": "system", "content": "You are a helpful AI bot."}, + {"role": "user", "name": "anne", "content": "Why is the sky blue?"}, + {"role": "assistant", "content": "The sky is blue because that's a great colour."}, + ] + print(test_messages) + + messages = oai_messages_to_bedrock_messages(test_messages, False, False) + print(messages) + + expected_messages = [ + {"role": "user", "content": [{"text": "You are a helpful AI bot."}]}, + {"role": "assistant", "content": [{"text": "Please continue."}]}, + {"role": "user", "content": [{"text": "Why is the sky blue?"}]}, + {"role": "assistant", "content": [{"text": "The sky is blue because that's a great colour."}]}, + {"role": "user", "content": [{"text": "Please continue."}]}, + ] + + assert messages == expected_messages, "'Please continue' message was not appended." diff --git a/website/docs/topics/non-openai-models/cloud-anthropic.ipynb b/website/docs/topics/non-openai-models/cloud-anthropic.ipynb index c5b757f8288b..a6c87b6a5ca5 100644 --- a/website/docs/topics/non-openai-models/cloud-anthropic.ipynb +++ b/website/docs/topics/non-openai-models/cloud-anthropic.ipynb @@ -21,7 +21,7 @@ "Additionally, this client class provides support for function/tool calling and will track token usage and cost correctly as per Anthropic's API costs (as of June 2024).\n", "\n", "## Requirements\n", - "To use Anthropic Claude with AutoGen, first you need to install the `pyautogen[\"anthropic]` package.\n", + "To use Anthropic Claude with AutoGen, first you need to install the `pyautogen[anthropic]` package.\n", "\n", "To try out the function call feature of Claude model, you need to install `anthropic>=0.23.1`.\n" ] @@ -32,7 +32,6 @@ "metadata": {}, "outputs": [], "source": [ - "# !pip install pyautogen\n", "!pip install pyautogen[\"anthropic\"]" ] }, diff --git a/website/docs/topics/non-openai-models/cloud-bedrock.ipynb b/website/docs/topics/non-openai-models/cloud-bedrock.ipynb new file mode 100644 index 000000000000..71c1e2e7ffe3 --- /dev/null +++ b/website/docs/topics/non-openai-models/cloud-bedrock.ipynb @@ -0,0 +1,1298 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# Amazon Bedrock\n", + "\n", + "AutoGen allows you to use Amazon's generative AI Bedrock service to run inference with a number of open-weight models and as well as their own models.\n", + "\n", + "Amazon Bedrock supports models from providers such as Meta, Anthropic, Cohere, and Mistral.\n", + "\n", + "In this notebook, we demonstrate how to use Anthropic's Sonnet model for AgentChat in AutoGen.\n", + "\n", + "## Model features / support\n", + "\n", + "Amazon Bedrock supports a wide range of models, not only for text generation but also for image classification and generation. Not all features are supported by AutoGen or by the Converse API used. Please see [Amazon's documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html#conversation-inference-supported-models-features) on the features supported by the Converse API.\n", + "\n", + "At this point in time AutoGen supports text generation and image classification (passing images to the LLM).\n", + "\n", + "It does not, yet, support image generation ([contribute](https://microsoft.github.io/autogen/docs/contributor-guide/contributing/)).\n", + "\n", + "## Requirements\n", + "To use Amazon Bedrock with AutoGen, first you need to install the `pyautogen[bedrock]` package.\n", + "\n", + "## Pricing\n", + "\n", + "When we combine the number of models supported and costs being on a per-region basis, it's not feasible to maintain the costs for each model+region combination within the AutoGen implementation. Therefore, it's recommended that you add the following to your config with cost per 1,000 input and output tokens, respectively:\n", + "```\n", + "{\n", + " ...\n", + " \"price\": [0.003, 0.015]\n", + " ...\n", + "}\n", + "```\n", + "\n", + "Amazon Bedrock pricing is available [here](https://aws.amazon.com/bedrock/pricing/)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# If you need to install AutoGen with Amazon Bedrock\n", + "!pip install pyautogen[\"bedrock\"]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Set the config for Amazon Bedrock\n", + "\n", + "Amazon's Bedrock does not use the `api_key` as per other cloud inference providers for authentication, instead it uses a number of access, token, and profile values. These fields will need to be added to your client configuration. Please check the Amazon Bedrock documentation to determine which ones you will need to add.\n", + "\n", + "The available parameters are:\n", + "\n", + "- aws_region (mandatory)\n", + "- aws_access_key (or environment variable: AWS_ACCESS_KEY)\n", + "- aws_secret_key (or environment variable: AWS_SECRET_KEY)\n", + "- aws_session_token (or environment variable: AWS_SESSION_TOKEN)\n", + "- aws_profile_name\n", + "\n", + "Beyond the authentication credentials, the only mandatory parameters are `api_type` and `model`.\n", + "\n", + "The following parameters are common across all models used:\n", + "\n", + "- temperature\n", + "- topP\n", + "- maxTokens\n", + "\n", + "You can also include parameters specific to the model you are using (see the model detail within Amazon's documentation for more information), the four supported additional parameters are:\n", + "\n", + "- top_p\n", + "- top_k\n", + "- k\n", + "- seed\n", + "\n", + "An additional parameter can be added that denotes whether the model supports a system prompt (which is where the system messages are not included in the message list, but in a separate parameter). This defaults to `True`, so set it to `False` if your model (for example Mistral's Instruct models) [doesn't support this feature](https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html#conversation-inference-supported-models-features):\n", + "\n", + "- supports_system_prompts\n", + "\n", + "It is important to add the `api_type` field and set it to a string that corresponds to the client type used: `bedrock`.\n", + "\n", + "Example:\n", + "```\n", + "[\n", + " {\n", + " \"api_type\": \"bedrock\",\n", + " \"model\": \"amazon.titan-text-premier-v1:0\",\n", + " \"aws_region\": \"us-east-1\"\n", + " \"aws_access_key\": \"\",\n", + " \"aws_secret_key\": \"\",\n", + " \"aws_session_token\": \"\",\n", + " \"aws_profile_name\": \"\",\n", + " },\n", + " {\n", + " \"api_type\": \"bedrock\",\n", + " \"model\": \"anthropic.claude-3-sonnet-20240229-v1:0\",\n", + " \"aws_region\": \"us-east-1\"\n", + " \"aws_access_key\": \"\",\n", + " \"aws_secret_key\": \"\",\n", + " \"aws_session_token\": \"\",\n", + " \"aws_profile_name\": \"\",\n", + " \"temperature\": 0.5,\n", + " \"topP\": 0.2,\n", + " \"maxTokens\": 250,\n", + " },\n", + " {\n", + " \"api_type\": \"bedrock\",\n", + " \"model\": \"mistral.mixtral-8x7b-instruct-v0:1\",\n", + " \"aws_region\": \"us-east-1\"\n", + " \"aws_access_key\": \"\",\n", + " \"aws_secret_key\": \"\",\n", + " \"supports_system_prompts\": False, # Mistral Instruct models don't support a separate system prompt\n", + " \"price\": [0.00045, 0.0007] # Specific pricing for this model/region\n", + " }\n", + "]\n", + "```" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Two-agent Coding Example" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Configuration\n", + "\n", + "Start with our configuration - we'll use Anthropic's Sonnet model and put in recent pricing. Additionally, we'll reduce the temperature to 0.1 so its responses are less varied." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": {}, + "outputs": [], + "source": [ + "from typing_extensions import Annotated\n", + "\n", + "import autogen\n", + "\n", + "config_list_bedrock = [\n", + " {\n", + " \"api_type\": \"bedrock\",\n", + " \"model\": \"anthropic.claude-3-sonnet-20240229-v1:0\",\n", + " \"aws_region\": \"us-east-1\",\n", + " \"aws_access_key\": \"[FILL THIS IN]\",\n", + " \"aws_secret_key\": \"[FILL THIS IN]\",\n", + " \"price\": [0.003, 0.015],\n", + " \"temperature\": 0.1,\n", + " \"cache_seed\": None, # turn off caching\n", + " }\n", + "]" + ] + }, + { + "attachments": {}, + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Construct Agents\n", + "\n", + "Construct a simple conversation between a User proxy and an ConversableAgent, which uses the Sonnet model.\n" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [], + "source": [ + "assistant = autogen.AssistantAgent(\n", + " \"assistant\",\n", + " llm_config={\n", + " \"config_list\": config_list_bedrock,\n", + " },\n", + ")\n", + "\n", + "user_proxy = autogen.UserProxyAgent(\n", + " \"user_proxy\",\n", + " human_input_mode=\"NEVER\",\n", + " code_execution_config={\n", + " \"work_dir\": \"coding\",\n", + " \"use_docker\": False,\n", + " },\n", + " is_termination_msg=lambda x: x.get(\"content\", \"\") and \"TERMINATE\" in x.get(\"content\", \"\"),\n", + " max_consecutive_auto_reply=1,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Initiate Chat" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33muser_proxy\u001b[0m (to assistant):\n", + "\n", + "Write a python program to print the first 10 numbers of the Fibonacci sequence. Just output the python code, no additional information.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33massistant\u001b[0m (to user_proxy):\n", + "\n", + "```python\n", + "# Define a function to calculate Fibonacci sequence\n", + "def fibonacci(n):\n", + " if n <= 0:\n", + " return []\n", + " elif n == 1:\n", + " return [0]\n", + " elif n == 2:\n", + " return [0, 1]\n", + " else:\n", + " sequence = [0, 1]\n", + " for i in range(2, n):\n", + " sequence.append(sequence[i-1] + sequence[i-2])\n", + " return sequence\n", + "\n", + "# Call the function to get the first 10 Fibonacci numbers\n", + "fib_sequence = fibonacci(10)\n", + "print(fib_sequence)\n", + "```\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> EXECUTING CODE BLOCK 0 (inferred language is python)...\u001b[0m\n", + "\u001b[33muser_proxy\u001b[0m (to assistant):\n", + "\n", + "exitcode: 0 (execution succeeded)\n", + "Code output: \n", + "[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]\n", + "\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33massistant\u001b[0m (to user_proxy):\n", + "\n", + "Great, the code executed successfully and printed the first 10 numbers of the Fibonacci sequence correctly.\n", + "\n", + "TERMINATE\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "data": { + "text/plain": [ + "ChatResult(chat_id=None, chat_history=[{'content': 'Write a python program to print the first 10 numbers of the Fibonacci sequence. Just output the python code, no additional information.', 'role': 'assistant'}, {'content': '```python\\n# Define a function to calculate Fibonacci sequence\\ndef fibonacci(n):\\n if n <= 0:\\n return []\\n elif n == 1:\\n return [0]\\n elif n == 2:\\n return [0, 1]\\n else:\\n sequence = [0, 1]\\n for i in range(2, n):\\n sequence.append(sequence[i-1] + sequence[i-2])\\n return sequence\\n\\n# Call the function to get the first 10 Fibonacci numbers\\nfib_sequence = fibonacci(10)\\nprint(fib_sequence)\\n```', 'role': 'user'}, {'content': 'exitcode: 0 (execution succeeded)\\nCode output: \\n[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]\\n', 'role': 'assistant'}, {'content': 'Great, the code executed successfully and printed the first 10 numbers of the Fibonacci sequence correctly.\\n\\nTERMINATE', 'role': 'user'}], summary='Great, the code executed successfully and printed the first 10 numbers of the Fibonacci sequence correctly.\\n\\n', cost={'usage_including_cached_inference': {'total_cost': 0.00624, 'anthropic.claude-3-sonnet-20240229-v1:0': {'cost': 0.00624, 'prompt_tokens': 1210, 'completion_tokens': 174, 'total_tokens': 1384}}, 'usage_excluding_cached_inference': {'total_cost': 0.00624, 'anthropic.claude-3-sonnet-20240229-v1:0': {'cost': 0.00624, 'prompt_tokens': 1210, 'completion_tokens': 174, 'total_tokens': 1384}}}, human_input=[])" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "user_proxy.initiate_chat(\n", + " assistant,\n", + " message=\"Write a python program to print the first 10 numbers of the Fibonacci sequence. Just output the python code, no additional information.\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tool Call Example\n", + "\n", + "In this example, instead of writing code, we will show how we can perform multiple tool calling with Meta's Llama 3.1 70B model, where it recommends calling more than one tool at a time.\n", + "\n", + "We'll use a simple travel agent assistant program where we have a couple of tools for weather and currency conversion." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Agents" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "from typing import Literal\n", + "\n", + "import autogen\n", + "\n", + "config_list_bedrock = [\n", + " {\n", + " \"api_type\": \"bedrock\",\n", + " \"model\": \"meta.llama3-1-70b-instruct-v1:0\",\n", + " \"aws_region\": \"us-west-2\",\n", + " \"aws_access_key\": \"[FILL THIS IN]\",\n", + " \"aws_secret_key\": \"[FILL THIS IN]\",\n", + " \"price\": [0.00265, 0.0035],\n", + " \"cache_seed\": None, # turn off caching\n", + " }\n", + "]\n", + "\n", + "# Create the agent and include examples of the function calling JSON in the prompt\n", + "# to help guide the model\n", + "chatbot = autogen.AssistantAgent(\n", + " name=\"chatbot\",\n", + " system_message=\"\"\"For currency exchange and weather forecasting tasks,\n", + " only use the functions you have been provided with.\n", + " Output only the word 'TERMINATE' when an answer has been provided.\n", + " Use both tools together if you can.\"\"\",\n", + " llm_config={\n", + " \"config_list\": config_list_bedrock,\n", + " },\n", + ")\n", + "\n", + "user_proxy = autogen.UserProxyAgent(\n", + " name=\"user_proxy\",\n", + " is_termination_msg=lambda x: x.get(\"content\", \"\") and \"TERMINATE\" in x.get(\"content\", \"\"),\n", + " human_input_mode=\"NEVER\",\n", + " max_consecutive_auto_reply=2,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Create the two functions, annotating them so that those descriptions can be passed through to the LLM.\n", + "\n", + "With Meta's Llama 3.1 models, they are more likely to pass a numeric parameter as a string, e.g. \"123.45\" instead of 123.45, so we'll convert numeric parameters from strings to floats if necessary.\n", + "\n", + "We associate them with the agents using `register_for_execution` for the user_proxy so it can execute the function and `register_for_llm` for the chatbot (powered by the LLM) so it can pass the function definitions to the LLM." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# Currency Exchange function\n", + "\n", + "CurrencySymbol = Literal[\"USD\", \"EUR\"]\n", + "\n", + "# Define our function that we expect to call\n", + "\n", + "\n", + "def exchange_rate(base_currency: CurrencySymbol, quote_currency: CurrencySymbol) -> float:\n", + " if base_currency == quote_currency:\n", + " return 1.0\n", + " elif base_currency == \"USD\" and quote_currency == \"EUR\":\n", + " return 1 / 1.1\n", + " elif base_currency == \"EUR\" and quote_currency == \"USD\":\n", + " return 1.1\n", + " else:\n", + " raise ValueError(f\"Unknown currencies {base_currency}, {quote_currency}\")\n", + "\n", + "\n", + "# Register the function with the agent\n", + "\n", + "\n", + "@user_proxy.register_for_execution()\n", + "@chatbot.register_for_llm(description=\"Currency exchange calculator.\")\n", + "def currency_calculator(\n", + " base_amount: Annotated[float, \"Amount of currency in base_currency, float values (no strings), e.g. 987.82\"],\n", + " base_currency: Annotated[CurrencySymbol, \"Base currency\"] = \"USD\",\n", + " quote_currency: Annotated[CurrencySymbol, \"Quote currency\"] = \"EUR\",\n", + ") -> str:\n", + " # If the amount is passed in as a string, e.g. \"123.45\", attempt to convert to a float\n", + " if isinstance(base_amount, str):\n", + " base_amount = float(base_amount)\n", + "\n", + " quote_amount = exchange_rate(base_currency, quote_currency) * base_amount\n", + " return f\"{format(quote_amount, '.2f')} {quote_currency}\"\n", + "\n", + "\n", + "# Weather function\n", + "\n", + "\n", + "# Example function to make available to model\n", + "def get_current_weather(location, unit=\"fahrenheit\"):\n", + " \"\"\"Get the weather for some location\"\"\"\n", + " if \"chicago\" in location.lower():\n", + " return json.dumps({\"location\": \"Chicago\", \"temperature\": \"13\", \"unit\": unit})\n", + " elif \"san francisco\" in location.lower():\n", + " return json.dumps({\"location\": \"San Francisco\", \"temperature\": \"55\", \"unit\": unit})\n", + " elif \"new york\" in location.lower():\n", + " return json.dumps({\"location\": \"New York\", \"temperature\": \"11\", \"unit\": unit})\n", + " else:\n", + " return json.dumps({\"location\": location, \"temperature\": \"unknown\"})\n", + "\n", + "\n", + "# Register the function with the agent\n", + "\n", + "\n", + "@user_proxy.register_for_execution()\n", + "@chatbot.register_for_llm(description=\"Weather forecast for US cities.\")\n", + "def weather_forecast(\n", + " location: Annotated[str, \"City name\"],\n", + ") -> str:\n", + " weather_details = get_current_weather(location=location)\n", + " weather = json.loads(weather_details)\n", + " return f\"{weather['location']} will be {weather['temperature']} degrees {weather['unit']}\"" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We pass through our customer's message and run the chat.\n", + "\n", + "Finally, we ask the LLM to summarise the chat and print that out." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33muser_proxy\u001b[0m (to chatbot):\n", + "\n", + "What's the weather in New York and can you tell me how much is 123.45 EUR in USD so I can spend it on my holiday?\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mchatbot\u001b[0m (to user_proxy):\n", + "\n", + "\n", + "\u001b[32m***** Suggested tool call (tooluse__h3d1AEDR3Sm2XRoGCjc2Q): weather_forecast *****\u001b[0m\n", + "Arguments: \n", + "{\"location\": \"New York\"}\n", + "\u001b[32m**********************************************************************************\u001b[0m\n", + "\u001b[32m***** Suggested tool call (tooluse_wrdda3wRRO-ugUY4qrv8YQ): currency_calculator *****\u001b[0m\n", + "Arguments: \n", + "{\"base_amount\": \"123\", \"base_currency\": \"EUR\", \"quote_currency\": \"USD\"}\n", + "\u001b[32m*************************************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[35m\n", + ">>>>>>>> EXECUTING FUNCTION weather_forecast...\u001b[0m\n", + "\u001b[35m\n", + ">>>>>>>> EXECUTING FUNCTION currency_calculator...\u001b[0m\n", + "\u001b[33muser_proxy\u001b[0m (to chatbot):\n", + "\n", + "\u001b[33muser_proxy\u001b[0m (to chatbot):\n", + "\n", + "\u001b[32m***** Response from calling tool (tooluse__h3d1AEDR3Sm2XRoGCjc2Q) *****\u001b[0m\n", + "New York will be 11 degrees fahrenheit\n", + "\u001b[32m***********************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33muser_proxy\u001b[0m (to chatbot):\n", + "\n", + "\u001b[32m***** Response from calling tool (tooluse_wrdda3wRRO-ugUY4qrv8YQ) *****\u001b[0m\n", + "135.30 USD\n", + "\u001b[32m***********************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mchatbot\u001b[0m (to user_proxy):\n", + "\n", + "\n", + "\n", + "TERMINATE\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\n", + "\n", + "The weather in New York is 11 degrees Fahrenheit. 123.45 EUR is equivalent to 135.30 USD.\n" + ] + } + ], + "source": [ + "# start the conversation\n", + "res = user_proxy.initiate_chat(\n", + " chatbot,\n", + " message=\"What's the weather in New York and can you tell me how much is 123.45 EUR in USD so I can spend it on my holiday?\",\n", + " summary_method=\"reflection_with_llm\",\n", + ")\n", + "\n", + "print(res.summary[\"content\"])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Group Chat Example with Anthropic's Claude 3 Sonnet, Mistral's Large 2, and Meta's Llama 3.1 70B\n", + "\n", + "The flexibility of using LLMs from the industry's leading providers, particularly larger models, with Amazon Bedrock allows you to use multiple of them in a single workflow.\n", + "\n", + "Here we have a conversation that has two models (Anthropic's Claude 3 Sonnet and Mistral's Large 2) debate each other with another as the judge (Meta's Llama 3.1 70B). Additionally, a tool call is made to pull through some mock news that they will debate on." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33muser_proxy\u001b[0m (to chat_manager):\n", + "\n", + "Analyze the potential of Anthropic and Mistral to revolutionize the field of AI based on today's headlines. Today is 06202024. Start by selecting 'research_assistant' to get relevant news articles and then ask sonnet_agent and mistral_agent to respond before the judge evaluates the conversation.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[32m\n", + "Next speaker: research_assistant\n", + "\u001b[0m\n", + "\u001b[33mresearch_assistant\u001b[0m (to chat_manager):\n", + "\n", + "\n", + "\u001b[32m***** Suggested tool call (tooluse_7lcHbL3TT5WHyTl8Ee0Kmg): get_headlines *****\u001b[0m\n", + "Arguments: \n", + "{\"headline_date\": \"06202024\"}\n", + "\u001b[32m*******************************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[32m\n", + "Next speaker: code_interpreter\n", + "\u001b[0m\n", + "\u001b[35m\n", + ">>>>>>>> EXECUTING FUNCTION get_headlines...\u001b[0m\n", + "\u001b[33mcode_interpreter\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[33mcode_interpreter\u001b[0m (to chat_manager):\n", + "\n", + "\u001b[32m***** Response from calling tool (tooluse_7lcHbL3TT5WHyTl8Ee0Kmg) *****\u001b[0m\n", + "Epic Duel of the Titans: Anthropic and Mistral Usher in a New Era of Text Generation Excellence.\n", + " In a groundbreaking revelation that has sent shockwaves through the AI industry, Anthropic has unveiled \n", + " their state-of-the-art text generation model, Sonnet, hailed as a monumental leap in artificial intelligence. \n", + " Almost simultaneously, Mistral countered with their equally formidable creation, Large 2, showcasing \n", + " unparalleled prowess in generating coherent and contextually rich text. This scintillating rivalry \n", + " between two AI behemoths promises to revolutionize the landscape of machine learning, heralding an \n", + " era of unprecedented creativity and sophistication in text generation that will reshape industries, \n", + " ignite innovation, and captivate minds worldwide.\n", + "\u001b[32m***********************************************************************\u001b[0m\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[32m\n", + "Next speaker: sonnet_agent\n", + "\u001b[0m\n", + "\u001b[33msonnet_agent\u001b[0m (to chat_manager):\n", + "\n", + "I am sonnet_agent:\n", + "\n", + "This headline showcases the incredible advancements Anthropic has made with our state-of-the-art text generation model, Sonnet. Our breakthrough natural language processing capabilities represent a quantum leap forward, allowing us to generate text of unparalleled coherence, nuance, and contextual richness. \n", + "\n", + "While our competitor Mistral has also unveiled an impressive model in Large 2, Anthropic's Sonnet stands apart through its ability to truly understand and reason about the subject matter it is generating text on. This deeper comprehension allows Sonnet to produce profoundly insightful and substantive content that goes far beyond mere parroting of data.\n", + "\n", + "The implications of this milestone are boundless. From revolutionizing how we communicate and interact with AI assistants, to unlocking new frontiers in creative writing, journalism, education and scientific research - Sonnet has the potential to completely transform industry after industry. Anthropic is poised to be the definitive leader driving this paradigm shift.\n", + "\n", + "I welcome the healthy competition from Mistral, as it will only spur us to continue raising the bar. But make no mistake - with Sonnet, the future of advanced AI belongs to Anthropic. We have crossed a monumental threshold, and will continue our trailblazing work to deliver AI solutions that redefine what is possible. The world is witnessing the dawn of a new era of unfathomable potential.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[32m\n", + "Next speaker: mistral_agent\n", + "\u001b[0m\n", + "\u001b[33mmistral_agent\u001b[0m (to chat_manager):\n", + "\n", + "I am mistral_agent:\n", + "\n", + "While it is commendable to see the progress made by Anthropic with their Sonnet model, it is essential to recognize the groundbreaking strides Mistral has taken with our Large 2 model. Large 2 represents not just an incremental improvement but a transformative leap in text generation capabilities, setting new benchmarks for coherence, contextual understanding, and creative expression.\n", + "\n", + "Unlike Sonnet, which focuses heavily on understanding and reasoning, Large 2 excels in both comprehension and the nuanced generation of text that is indistinguishable from human writing. This balance allows Large 2 to produce content that is not only insightful but also incredibly engaging and natural, making it an invaluable tool across a broad spectrum of applications.\n", + "\n", + "The potential of Large 2 extends far beyond traditional text generation. It can revolutionize fields such as content creation, customer service, marketing, and even personalized learning experiences. Our model's ability to adapt to various contexts and generate contextually rich responses makes it a versatile and powerful tool for any industry looking to harness the power of AI.\n", + "\n", + "While we appreciate the competition from Anthropic, we firmly believe that Large 2 stands at the forefront of AI innovation. The future of AI is not just about understanding and reasoning; it's about creating content that resonates with people on a deep level. With Large 2, Mistral is paving the way for a future where AI-generated text is not just functional but also profoundly human-like.\n", + "\n", + "Pass to the judge.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[32m\n", + "Next speaker: judge\n", + "\u001b[0m\n", + "\u001b[33mjudge\u001b[0m (to chat_manager):\n", + "\n", + "\n", + "\n", + "After carefully evaluating the arguments presented by both sonnet_agent and mistral_agent, I have reached a decision.\n", + "\n", + "Both Anthropic's Sonnet and Mistral's Large 2 have demonstrated remarkable advancements in text generation capabilities, showcasing the potential to revolutionize various industries and transform the way we interact with AI.\n", + "\n", + "However, upon closer examination, I find that mistral_agent's argument presents a more convincing case for why Large 2 stands at the forefront of AI innovation. The emphasis on balance between comprehension and nuanced generation of text that is indistinguishable from human writing sets Large 2 apart. This balance is crucial for creating content that is not only insightful but also engaging and natural, making it a versatile tool across a broad spectrum of applications.\n", + "\n", + "Furthermore, mistral_agent's argument highlights the potential of Large 2 to revolutionize fields beyond traditional text generation, such as content creation, customer service, marketing, and personalized learning experiences. This versatility and adaptability make Large 2 a powerful tool for any industry looking to harness the power of AI.\n", + "\n", + "In contrast, while sonnet_agent's argument showcases the impressive capabilities of Sonnet, it focuses heavily on understanding and reasoning, which, although important, may not be enough to set it apart from Large 2.\n", + "\n", + "Therefore, based on the arguments presented, I conclude that Mistral's Large 2 has the potential to revolutionize the field of AI more significantly than Anthropic's Sonnet.\n", + "\n", + "TERMINATE.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[32m\n", + "Next speaker: code_interpreter\n", + "\u001b[0m\n" + ] + }, + { + "data": { + "text/plain": [ + "ChatResult(chat_id=None, chat_history=[{'content': \"Analyze the potential of Anthropic and Mistral to revolutionize the field of AI based on today's headlines. Today is 06202024. Start by selecting 'research_assistant' to get relevant news articles and then ask sonnet_agent and mistral_agent to respond before the judge evaluates the conversation.\", 'role': 'assistant'}], summary=\"Analyze the potential of Anthropic and Mistral to revolutionize the field of AI based on today's headlines. Today is 06202024. Start by selecting 'research_assistant' to get relevant news articles and then ask sonnet_agent and mistral_agent to respond before the judge evaluates the conversation.\", cost={'usage_including_cached_inference': {'total_cost': 0}, 'usage_excluding_cached_inference': {'total_cost': 0}}, human_input=[])" + ] + }, + "execution_count": 22, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from typing import Annotated, Literal\n", + "\n", + "import autogen\n", + "from autogen import AssistantAgent, GroupChat, GroupChatManager, UserProxyAgent\n", + "\n", + "config_list_sonnet = [\n", + " {\n", + " \"api_type\": \"bedrock\",\n", + " \"model\": \"anthropic.claude-3-sonnet-20240229-v1:0\",\n", + " \"aws_region\": \"us-east-1\",\n", + " \"aws_access_key\": \"[FILL THIS IN]\",\n", + " \"aws_secret_key\": \"[FILL THIS IN]\",\n", + " \"price\": [0.003, 0.015],\n", + " \"temperature\": 0.1,\n", + " \"cache_seed\": None, # turn off caching\n", + " }\n", + "]\n", + "\n", + "config_list_mistral = [\n", + " {\n", + " \"api_type\": \"bedrock\",\n", + " \"model\": \"mistral.mistral-large-2407-v1:0\",\n", + " \"aws_region\": \"us-west-2\",\n", + " \"aws_access_key\": \"[FILL THIS IN]\",\n", + " \"aws_secret_key\": \"[FILL THIS IN]\",\n", + " \"price\": [0.003, 0.009],\n", + " \"temperature\": 0.1,\n", + " \"cache_seed\": None, # turn off caching\n", + " }\n", + "]\n", + "\n", + "config_list_llama31_70b = [\n", + " {\n", + " \"api_type\": \"bedrock\",\n", + " \"model\": \"meta.llama3-1-70b-instruct-v1:0\",\n", + " \"aws_region\": \"us-west-2\",\n", + " \"aws_access_key\": \"[FILL THIS IN]\",\n", + " \"aws_secret_key\": \"[FILL THIS IN]\",\n", + " \"price\": [0.00265, 0.0035],\n", + " \"temperature\": 0.1,\n", + " \"cache_seed\": None, # turn off caching\n", + " }\n", + "]\n", + "\n", + "alice = AssistantAgent(\n", + " \"sonnet_agent\",\n", + " system_message=\"You are from Anthropic, an AI company that created the Sonnet large language model. You make arguments to support your company's position. You analyse given text. You are not a programmer and don't use Python. Pass to mistral_agent when you have finished. Start your response with 'I am sonnet_agent'.\",\n", + " llm_config={\n", + " \"config_list\": config_list_sonnet,\n", + " },\n", + " is_termination_msg=lambda x: x.get(\"content\", \"\").find(\"TERMINATE\") >= 0,\n", + ")\n", + "\n", + "bob = autogen.AssistantAgent(\n", + " \"mistral_agent\",\n", + " system_message=\"You are from Mistral, an AI company that created the Large v2 large language model. You make arguments to support your company's position. You analyse given text. You are not a programmer and don't use Python. Pass to the judge if you have finished. Start your response with 'I am mistral_agent'.\",\n", + " llm_config={\n", + " \"config_list\": config_list_mistral,\n", + " },\n", + " is_termination_msg=lambda x: x.get(\"content\", \"\").find(\"TERMINATE\") >= 0,\n", + ")\n", + "\n", + "charlie = AssistantAgent(\n", + " \"research_assistant\",\n", + " system_message=\"You are a helpful assistant to research the latest news and headlines. You have access to call functions to get the latest news articles for research through 'code_interpreter'.\",\n", + " llm_config={\n", + " \"config_list\": config_list_llama31_70b,\n", + " },\n", + " is_termination_msg=lambda x: x.get(\"content\", \"\").find(\"TERMINATE\") >= 0,\n", + ")\n", + "\n", + "dan = AssistantAgent(\n", + " \"judge\",\n", + " system_message=\"You are a judge. You will evaluate the arguments and make a decision on which one is more convincing. End your decision with the word 'TERMINATE' to conclude the debate.\",\n", + " llm_config={\n", + " \"config_list\": config_list_llama31_70b,\n", + " },\n", + " is_termination_msg=lambda x: x.get(\"content\", \"\").find(\"TERMINATE\") >= 0,\n", + ")\n", + "\n", + "code_interpreter = UserProxyAgent(\n", + " \"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", + "\n", + "@code_interpreter.register_for_execution() # Decorator factory for registering a function to be executed by an agent\n", + "@charlie.register_for_llm(\n", + " name=\"get_headlines\", description=\"Get the headline of a particular day.\"\n", + ") # Decorator factory for registering a function to be used by an agent\n", + "def get_headlines(headline_date: Annotated[str, \"Date in MMDDYY format, e.g., 06192024\"]) -> str:\n", + " mock_news = {\n", + " \"06202024\": \"\"\"Epic Duel of the Titans: Anthropic and Mistral Usher in a New Era of Text Generation Excellence.\n", + " In a groundbreaking revelation that has sent shockwaves through the AI industry, Anthropic has unveiled\n", + " their state-of-the-art text generation model, Sonnet, hailed as a monumental leap in artificial intelligence.\n", + " Almost simultaneously, Mistral countered with their equally formidable creation, Large 2, showcasing\n", + " unparalleled prowess in generating coherent and contextually rich text. This scintillating rivalry\n", + " between two AI behemoths promises to revolutionize the landscape of machine learning, heralding an\n", + " era of unprecedented creativity and sophistication in text generation that will reshape industries,\n", + " ignite innovation, and captivate minds worldwide.\"\"\",\n", + " \"06192024\": \"OpenAI founder Sutskever sets up new AI company devoted to safe superintelligence.\",\n", + " }\n", + " return mock_news.get(headline_date, \"No news available for today.\")\n", + "\n", + "\n", + "user_proxy = UserProxyAgent(\n", + " \"user_proxy\",\n", + " human_input_mode=\"NEVER\",\n", + " code_execution_config=False,\n", + " default_auto_reply=\"\",\n", + " is_termination_msg=lambda x: x.get(\"content\", \"\").find(\"TERMINATE\") >= 0,\n", + ")\n", + "\n", + "groupchat = GroupChat(\n", + " agents=[alice, bob, charlie, dan, code_interpreter],\n", + " messages=[],\n", + " allow_repeat_speaker=False,\n", + " max_round=10,\n", + ")\n", + "\n", + "manager = GroupChatManager(\n", + " groupchat=groupchat,\n", + " llm_config={\n", + " \"config_list\": config_list_llama31_70b,\n", + " },\n", + ")\n", + "\n", + "task = \"Analyze the potential of Anthropic and Mistral to revolutionize the field of AI based on today's headlines. Today is 06202024. Start by selecting 'research_assistant' to get relevant news articles and then ask sonnet_agent and mistral_agent to respond before the judge evaluates the conversation.\"\n", + "\n", + "user_proxy.initiate_chat(manager, message=task)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "And there we have it, a number of different LLMs all collaborating together on a single cloud platform." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Image classification with Anthropic's Claude 3 Sonnet\n", + "\n", + "AutoGen's Amazon Bedrock client class supports inputting images for the LLM to respond to.\n", + "\n", + "In this simple example, we'll use an image on the Internet and send it to Anthropic's Claude 3 Sonnet model to describe.\n", + "\n", + "Here's the image we'll use:\n", + "\n", + "![I -heart- AutoGen](https://microsoft.github.io/autogen/assets/images/love-ec54b2666729d3e9d93f91773d1a77cf.png \"width=400 height=400\")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "config_list_sonnet = {\n", + " \"config_list\": [\n", + " {\n", + " \"api_type\": \"bedrock\",\n", + " \"model\": \"anthropic.claude-3-sonnet-20240229-v1:0\",\n", + " \"aws_region\": \"us-east-1\",\n", + " \"aws_access_key\": \"[FILL THIS IN]\",\n", + " \"aws_secret_key\": \"[FILL THIS IN]\",\n", + " \"cache_seed\": None,\n", + " }\n", + " ]\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We'll use a Multimodal agent to handle the image" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [], + "source": [ + "import autogen\n", + "from autogen import Agent, AssistantAgent, ConversableAgent, UserProxyAgent\n", + "from autogen.agentchat.contrib.capabilities.vision_capability import VisionCapability\n", + "from autogen.agentchat.contrib.img_utils import get_pil_image, pil_to_data_uri\n", + "from autogen.agentchat.contrib.multimodal_conversable_agent import MultimodalConversableAgent\n", + "from autogen.code_utils import content_str\n", + "\n", + "image_agent = MultimodalConversableAgent(\n", + " name=\"image-explainer\",\n", + " max_consecutive_auto_reply=10,\n", + " llm_config=config_list_sonnet,\n", + ")\n", + "\n", + "user_proxy = autogen.UserProxyAgent(\n", + " name=\"User_proxy\",\n", + " system_message=\"A human admin.\",\n", + " human_input_mode=\"NEVER\",\n", + " max_consecutive_auto_reply=0,\n", + " code_execution_config={\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": "markdown", + "metadata": {}, + "source": [ + "We start the chat and use the `img` tag in the message. The image will be downloaded and converted to bytes, then sent to the LLM." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mUser_proxy\u001b[0m (to image-explainer):\n", + "\n", + "What's happening in this image?\n", + ".\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", + "\u001b[33mimage-explainer\u001b[0m (to User_proxy):\n", + "\n", + "This image appears to be an advertisement or promotional material for a company called Autogen. The central figure is a stylized robot or android holding up a signboard with the company's name on it. The signboard also features a colorful heart design made up of many smaller hearts, suggesting themes related to love, care, or affection. The robot has a friendly, cartoonish expression with a large blue eye or lens. The overall style and color scheme give it a vibrant, eye-catching look that likely aims to portray Autogen as an innovative, approachable technology brand focused on connecting with people.\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + } + ], + "source": [ + "# Ask the image_agent to describe the image\n", + "result = user_proxy.initiate_chat(\n", + " image_agent,\n", + " message=\"\"\"What's happening in this image?\n", + ".\"\"\",\n", + ")" + ] + } + ], + "metadata": { + "front_matter": { + "description": "Define and load a custom model", + "tags": [ + "custom model" + ] + }, + "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.11.9" + }, + "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/website/docs/topics/non-openai-models/cloud-cohere.ipynb b/website/docs/topics/non-openai-models/cloud-cohere.ipynb index fed5911475f4..b678810a7699 100644 --- a/website/docs/topics/non-openai-models/cloud-cohere.ipynb +++ b/website/docs/topics/non-openai-models/cloud-cohere.ipynb @@ -421,7 +421,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "We pass through our customers message and run the chat.\n", + "We pass through our customer's message and run the chat.\n", "\n", "Finally, we ask the LLM to summarise the chat and print that out." ] From 3aa0528a12a263b675ef1ad614601436ef6f25f8 Mon Sep 17 00:00:00 2001 From: zcipod <45870019+zcipod@users.noreply.github.com> Date: Mon, 26 Aug 2024 17:31:36 +1000 Subject: [PATCH 14/16] fix `ImportFromModule` is unhashable issue (#3362) * ImportFromModule is unhashable This fix makes the conversion to string prior to the deduplication to avoid this issue * add type annotation for global_imports * meet code formatting check --------- Co-authored-by: zcipod Co-authored-by: Li Jiang --- autogen/coding/func_with_reqs.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/autogen/coding/func_with_reqs.py b/autogen/coding/func_with_reqs.py index 6f199573822b..f255f1df0179 100644 --- a/autogen/coding/func_with_reqs.py +++ b/autogen/coding/func_with_reqs.py @@ -6,7 +6,7 @@ from dataclasses import dataclass, field from importlib.abc import SourceLoader from textwrap import dedent, indent -from typing import Any, Callable, Generic, List, TypeVar, Union +from typing import Any, Callable, Generic, List, Set, TypeVar, Union from typing_extensions import ParamSpec @@ -159,12 +159,12 @@ def _build_python_functions_file( funcs: List[Union[FunctionWithRequirements[Any, P], Callable[..., Any], FunctionWithRequirementsStr]] ) -> str: # First collect all global imports - global_imports = set() + global_imports: Set[str] = set() for func in funcs: if isinstance(func, (FunctionWithRequirements, FunctionWithRequirementsStr)): - global_imports.update(func.global_imports) + global_imports.update(map(_import_to_str, func.global_imports)) - content = "\n".join(map(_import_to_str, global_imports)) + "\n\n" + content = "\n".join(global_imports) + "\n\n" for func in funcs: content += _to_code(func) + "\n\n" From ed47ec9d4bb0a07a59f8d99f987a0d51672d2a0c Mon Sep 17 00:00:00 2001 From: Mark Sze <66362098+marklysze@users.noreply.github.com> Date: Mon, 26 Aug 2024 17:35:27 +1000 Subject: [PATCH 15/16] Transform to add an agent's name into the message content (#3334) * Initial commit with ability to add name into content with a transform * Transforms documentation * Fix transform links in documentation --------- Co-authored-by: Li Jiang --- .../contrib/capabilities/transforms.py | 92 ++ .../contrib/capabilities/test_transforms.py | 94 ++ .../transforms-for-nonopenai-models.ipynb | 933 ++++++++++++++++++ 3 files changed, 1119 insertions(+) create mode 100644 website/docs/topics/non-openai-models/transforms-for-nonopenai-models.ipynb diff --git a/autogen/agentchat/contrib/capabilities/transforms.py b/autogen/agentchat/contrib/capabilities/transforms.py index 7cd7fdb92a35..d9ad365b91b3 100644 --- a/autogen/agentchat/contrib/capabilities/transforms.py +++ b/autogen/agentchat/contrib/capabilities/transforms.py @@ -445,3 +445,95 @@ def _compress_text(self, text: str) -> Tuple[str, int]: def _validate_min_tokens(self, min_tokens: Optional[int]): if min_tokens is not None and min_tokens <= 0: raise ValueError("min_tokens must be greater than 0 or None") + + +class TextMessageContentName: + """A transform for including the agent's name in the content of a message.""" + + def __init__( + self, + position: str = "start", + format_string: str = "{name}:\n", + deduplicate: bool = True, + filter_dict: Optional[Dict] = None, + exclude_filter: bool = True, + ): + """ + Args: + position (str): The position to add the name to the content. The possible options are 'start' or 'end'. Defaults to 'start'. + format_string (str): The f-string to format the message name with. Use '{name}' as a placeholder for the agent's name. Defaults to '{name}:\n' and must contain '{name}'. + deduplicate (bool): Whether to deduplicate the formatted string so it doesn't appear twice (sometimes the LLM will add it to new messages itself). Defaults to True. + filter_dict (None or dict): A dictionary to filter out messages that you want/don't want to compress. + If None, no filters will be applied. + exclude_filter (bool): If exclude filter is True (the default value), messages that match the filter will be + excluded from compression. If False, messages that match the filter will be compressed. + """ + + assert isinstance(position, str) and position is not None + assert position in ["start", "end"] + assert isinstance(format_string, str) and format_string is not None + assert "{name}" in format_string + assert isinstance(deduplicate, bool) and deduplicate is not None + + self._position = position + self._format_string = format_string + self._deduplicate = deduplicate + self._filter_dict = filter_dict + self._exclude_filter = exclude_filter + + # Track the number of messages changed for logging + self._messages_changed = 0 + + def apply_transform(self, messages: List[Dict]) -> List[Dict]: + """Applies the name change to the message based on the position and format string. + + Args: + messages (List[Dict]): A list of message dictionaries. + + Returns: + List[Dict]: A list of dictionaries with the message content updated with names. + """ + # Make sure there is at least one message + if not messages: + return messages + + messages_changed = 0 + processed_messages = copy.deepcopy(messages) + for message in processed_messages: + # Some messages may not have content. + if not transforms_util.is_content_right_type( + message.get("content") + ) or not transforms_util.is_content_right_type(message.get("name")): + continue + + if not transforms_util.should_transform_message(message, self._filter_dict, self._exclude_filter): + continue + + if transforms_util.is_content_text_empty(message["content"]) or transforms_util.is_content_text_empty( + message["name"] + ): + continue + + # Get and format the name in the content + content = message["content"] + formatted_name = self._format_string.format(name=message["name"]) + + if self._position == "start": + if not self._deduplicate or not content.startswith(formatted_name): + message["content"] = f"{formatted_name}{content}" + + messages_changed += 1 + else: + if not self._deduplicate or not content.endswith(formatted_name): + message["content"] = f"{content}{formatted_name}" + + messages_changed += 1 + + self._messages_changed = messages_changed + return processed_messages + + def get_logs(self, pre_transform_messages: List[Dict], post_transform_messages: List[Dict]) -> Tuple[str, bool]: + if self._messages_changed > 0: + return f"{self._messages_changed} message(s) changed to incorporate name.", True + else: + return "No messages changed to incorporate name.", False diff --git a/test/agentchat/contrib/capabilities/test_transforms.py b/test/agentchat/contrib/capabilities/test_transforms.py index 34094a0008b7..cb2d798c4252 100644 --- a/test/agentchat/contrib/capabilities/test_transforms.py +++ b/test/agentchat/contrib/capabilities/test_transforms.py @@ -9,6 +9,7 @@ MessageHistoryLimiter, MessageTokenLimiter, TextMessageCompressor, + TextMessageContentName, ) from autogen.agentchat.contrib.capabilities.transforms_util import count_text_tokens @@ -60,6 +61,42 @@ def get_tool_messages_kept() -> List[Dict]: ] +def get_messages_with_names() -> List[Dict]: + return [ + {"role": "system", "content": "I am the system."}, + {"role": "user", "name": "charlie", "content": "I think the sky is blue."}, + {"role": "user", "name": "mary", "content": "The sky is red."}, + {"role": "user", "name": "bob", "content": "The sky is crimson."}, + ] + + +def get_messages_with_names_post_start() -> List[Dict]: + return [ + {"role": "system", "content": "I am the system."}, + {"role": "user", "name": "charlie", "content": "'charlie' said:\nI think the sky is blue."}, + {"role": "user", "name": "mary", "content": "'mary' said:\nThe sky is red."}, + {"role": "user", "name": "bob", "content": "'bob' said:\nThe sky is crimson."}, + ] + + +def get_messages_with_names_post_end() -> List[Dict]: + return [ + {"role": "system", "content": "I am the system."}, + {"role": "user", "name": "charlie", "content": "I think the sky is blue.\n(said 'charlie')"}, + {"role": "user", "name": "mary", "content": "The sky is red.\n(said 'mary')"}, + {"role": "user", "name": "bob", "content": "The sky is crimson.\n(said 'bob')"}, + ] + + +def get_messages_with_names_post_filtered() -> List[Dict]: + return [ + {"role": "system", "content": "I am the system."}, + {"role": "user", "name": "charlie", "content": "I think the sky is blue."}, + {"role": "user", "name": "mary", "content": "'mary' said:\nThe sky is red."}, + {"role": "user", "name": "bob", "content": "'bob' said:\nThe sky is crimson."}, + ] + + def get_text_compressors() -> List[TextCompressor]: compressors: List[TextCompressor] = [_MockTextCompressor()] try: @@ -300,6 +337,63 @@ def test_text_compression_with_filter(messages, text_compressor): assert _filter_dict_test(post_transform, pre_transform, ["user"], exclude_filter=False) +@pytest.mark.parametrize("messages", [get_messages_with_names()]) +def test_message_content_name(messages): + # Test including content name in messages + + # Add name at the start with format: "'{name}' said:\n" + content_transform = TextMessageContentName(position="start", format_string="'{name}' said:\n") + transformed_messages = content_transform.apply_transform(messages=messages) + + assert transformed_messages == get_messages_with_names_post_start() + + # Add name at the end with format: "\n(said '{name}')" + content_transform = TextMessageContentName(position="end", format_string="\n(said '{name}')") + transformed_messages_end = content_transform.apply_transform(messages=messages) + + assert transformed_messages_end == get_messages_with_names_post_end() + + # Test filtering out exclusion + content_transform = TextMessageContentName( + position="start", + format_string="'{name}' said:\n", + filter_dict={"name": ["charlie"]}, + exclude_filter=True, # Exclude + ) + + transformed_messages_end = content_transform.apply_transform(messages=messages) + + assert transformed_messages_end == get_messages_with_names_post_filtered() + + # Test filtering (inclusion) + content_transform = TextMessageContentName( + position="start", + format_string="'{name}' said:\n", + filter_dict={"name": ["mary", "bob"]}, + exclude_filter=False, # Include + ) + + transformed_messages_end = content_transform.apply_transform(messages=messages) + + assert transformed_messages_end == get_messages_with_names_post_filtered() + + # Test instantiation + with pytest.raises(AssertionError): + TextMessageContentName(position=123) # Invalid type for position + + with pytest.raises(AssertionError): + TextMessageContentName(position="middle") # Invalid value for position + + with pytest.raises(AssertionError): + TextMessageContentName(format_string=123) # Invalid type for format_string + + with pytest.raises(AssertionError): + TextMessageContentName(format_string="Agent:\n") # Missing '{name}' in format_string + + with pytest.raises(AssertionError): + TextMessageContentName(deduplicate="yes") # Invalid type for deduplicate + + if __name__ == "__main__": long_messages = get_long_messages() short_messages = get_short_messages() diff --git a/website/docs/topics/non-openai-models/transforms-for-nonopenai-models.ipynb b/website/docs/topics/non-openai-models/transforms-for-nonopenai-models.ipynb new file mode 100644 index 000000000000..88f651aa16de --- /dev/null +++ b/website/docs/topics/non-openai-models/transforms-for-nonopenai-models.ipynb @@ -0,0 +1,933 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Transform Messages for Non-OpenAI Models\n", + "\n", + "There are a large variety of models available beyond OpenAI's and they all have different capabilities. Smaller context windows and different API's can require tweaks to your workflow in order to work with them.\n", + "\n", + "If you're new to Transform Messages, see the [introduction to Transform Messages](/docs/topics/handling_long_contexts/intro_to_transform_messages).\n", + "\n", + "## Reducing context sizes\n", + "\n", + "Although context windows are increasing, there are still a large number of models that have context windows (e.g. 2K, 4K, or 8K tokens) which may be overwhelmed by your workflow's messages.\n", + "\n", + "To handle longer contexts using transforms, essentially reducing them effectively for smaller context windows, please see the page on [compressing text](/docs/topics/handling_long_contexts/compressing_text_w_llmligua).\n", + "\n", + "## Incorporating an agent's name\n", + "\n", + "Interestingly, the agent's name, such as Jack in the below example, is not included in messages when using non-OpenAI models. This means that there is no way of the name being known by the model during inference, unless we include it in the body of the message text.\n", + "\n", + "```python\n", + "comedian = ConversableAgent(\n", + " name=\"Jack\", # Not included in messages for non-OpenAI inference\n", + " llm_config=phi2,\n", + " system_message=\"Your name is Jack and you are a comedian.\",\n", + ")\n", + "```\n", + "\n", + "When using OpenAI models, the name field is included and examples in the AutoGen documentation may rely on this fact. Therefore, it may not be an issue in your existing workflows, however it's important to be aware of and be able to cater for it.\n", + "\n", + "In the simple two-agent chat example, below, we will use a `TextMessageContentName` transform, available from the Transforms module, to add in the name of the agents to the messages.\n", + "\n", + "As we won't be using OpenAI, we will use the Anthropic client to demonstrate.\n", + "\n", + "We'll start by importing our modules and setting our config." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/usr/local/lib/python3.11/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html\n", + " from .autonotebook import tqdm as notebook_tqdm\n" + ] + } + ], + "source": [ + "import os\n", + "\n", + "from autogen import ConversableAgent\n", + "\n", + "config_list_claude = [\n", + " {\n", + " \"model\": \"claude-3-5-sonnet-20240620\",\n", + " \"api_key\": os.getenv(\"ANTHROPIC_API_KEY\"),\n", + " \"api_type\": \"anthropic\",\n", + " \"cache_seed\": None, # Ensure we're not caching any results\n", + " }\n", + "]" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we add two agents, both comedians who will make up jokes about the other using their name." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "cathy = ConversableAgent(\n", + " \"Cathy\",\n", + " system_message=\"Your name is Cathy and you are a part of a duo of comedians.\",\n", + " llm_config={\"config_list\": config_list_claude},\n", + " human_input_mode=\"NEVER\",\n", + ")\n", + "\n", + "joe = ConversableAgent(\n", + " \"Joe\",\n", + " system_message=\"Your name is Joe and you are a part of a duo of comedians.\",\n", + " llm_config={\"config_list\": config_list_claude},\n", + " human_input_mode=\"NEVER\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's start the chat without using the transform and see how it performs." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mJoe\u001b[0m (to Cathy):\n", + "\n", + "People say I have a funny name, tell me a joke about it.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mCathy\u001b[0m (to Joe):\n", + "\n", + "As Cathy, part of a comedy duo, I'd need to know your actual name to craft a personalized joke about it. Without that information, I can't create a specific joke tailored to your name. However, I can offer a more general joke about names if you'd like. Let me know if you want to share your name or if you'd prefer a different kind of joke!\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mJoe\u001b[0m (to Cathy):\n", + "\n", + "I apologize for the confusion. You're right that as Cathy, you wouldn't know my name or background. I made a mistake in my previous response by assuming a context that wasn't provided. Thank you for the professional way you handled that.\n", + "\n", + "As Joe, the other half of your comedy duo, I'll play along with your prompt:\n", + "\n", + "\"Well, Cathy, you know what they say - people in glass houses shouldn't throw stones. But with a name like yours, maybe you should consider moving into a stone house! At least then when people say 'Cathy who?' you can say 'Cathy Stone, of course!'\"\n", + "\n", + "How's that for a quick improvised joke about your name, partner?\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mCathy\u001b[0m (to Joe):\n", + "\n", + "Oh Joe, you crack me up! That's why we're such a great duo. I love how you turned that around on me. Let me give it a shot:\n", + "\n", + "\"Well, Joe, speaking of stones, I guess with a partner like you, I'm stuck between a rock and a hard place. But hey, at least with you around, I'll never be accused of being the average Joe in this act!\"\n", + "\n", + "How's that for a comeback, partner? I think we're really on a roll here. Should we take this act on the road?\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + } + ], + "source": [ + "result = joe.initiate_chat(cathy, message=\"People say I have a funny name, tell me a joke about it.\", max_turns=2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we can see from the chat, Cathy doesn't know Joe's name at the start.\n", + "\n", + "Now, we'll create a transform that injects the names into the messages and apply that transform to both agents." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# Import our transforms\n", + "from autogen.agentchat.contrib.capabilities import transform_messages\n", + "from autogen.agentchat.contrib.capabilities.transforms import TextMessageContentName\n", + "\n", + "# Create a name transform\n", + "# This will inject the agent's name for a message into the start of the message content.\n", + "# E.g. \"'Jack' said\\n...\"\n", + "name_transform = TextMessageContentName(position=\"start\", format_string=\"'{name}' said:\\n\")\n", + "\n", + "# Create the TransformMessages that will be applied.\n", + "# In this case we are only putting in one transform but you could\n", + "# stack the transforms if you also wanted to do others, like\n", + "# compress the text. Transforms are performed sequentially.\n", + "context_handling = transform_messages.TransformMessages(transforms=[name_transform])\n", + "\n", + "# Add it to both agents so when they run inference it will apply to the messages\n", + "context_handling.add_to_agent(cathy)\n", + "context_handling.add_to_agent(joe)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let's try that chat again now that we're injecting the names." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mJoe\u001b[0m (to Cathy):\n", + "\n", + "People say I have a funny name, tell me a joke about it.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33m1 message(s) changed to incorporate name.\u001b[0m\n", + "\u001b[33mCathy\u001b[0m (to Joe):\n", + "\n", + "Hey there Joe! I'm Cathy, one half of a comedy duo. You know, having a short name like Joe can be pretty fun to play with. Here's a little joke for you:\n", + "\n", + "Why did Joe's friends call him \"Volcano\"?\n", + "Because he was always erupting with short outbursts!\n", + "\n", + "Ba dumb tss! Okay, maybe not my best work, but I promise our duo's material is much funnier on stage. Names can be great comedy fodder - short ones, long ones, unusual ones. The key is finding the right angle. Got any funny stories about your name you'd like to share? Those personal anecdotes often make for the best laughs!\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33m2 message(s) changed to incorporate name.\u001b[0m\n", + "\u001b[33mJoe\u001b[0m (to Cathy):\n", + "\n", + "Thanks for the setup, Cathy! I like your joke - short and sweet, just like my name. Speaking of my name, here's a little quip I've used before:\n", + "\n", + "You know, I've always felt my parents really phoned it in when naming me. They must've been like, \"We need to name this kid... eh, Joe. Done. What's for dinner?\"\n", + "\n", + "But hey, at least it's easy to spell. Although sometimes I wonder if I should jazz it up a bit. Maybe go by \"Jo-seph\" or \"Joe-tastic.\" What do you think, Cathy? Any suggestions for spicing up a plain old \"Joe\"?\n", + "\n", + "And you're right, personal stories about names can be comedy gold. I once had a telemarketer absolutely butcher my name. They called and asked for \"Joo.\" I told them there's no Joo here, just a Joe. They apologized and asked for \"Hoe\" instead. At that point, I just had to laugh and hang up!\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33m3 message(s) changed to incorporate name.\u001b[0m\n", + "\u001b[33mCathy\u001b[0m (to Joe):\n", + "\n", + "'Cathy' said:\n", + "Oh Joe, you're a natural! I'm loving your material. That telemarketer story had me in stitches - from Joe to Joo to Hoe, what a wild ride! \n", + "\n", + "As for jazzing up your name, I've got a few suggestions that might tickle your funny bone:\n", + "\n", + "1. \"Joe-normous\" - for when you're feeling larger than life.\n", + "2. \"Joevius Maximus\" - if you're going for that Roman emperor vibe.\n", + "3. \"Joe-pacabra\" - half man, half mysterious creature.\n", + "4. \"Joehemoth\" - for those days when you feel particularly beastly.\n", + "5. \"Average Joe-seidon\" - god of the sea... and mediocrity.\n", + "\n", + "But honestly, Joe, I think you're selling yourself short (pun intended for your short name). Your delivery is spot-on, and you've got a great sense of timing. Have you ever considered doing stand-up? With material like that, you could be the next big thing in comedy. \n", + "\n", + "Just imagine the headline: \"Plain Old Joe Takes Comedy World by Storm!\" Now that's a name that would turn heads, don't you think?\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + } + ], + "source": [ + "result = joe.initiate_chat(cathy, message=\"People say I have a funny name, tell me a joke about it.\", max_turns=2)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see from this conversation that Cathy uses Joe's name in her first response message, showing that incorporating the name using a transform has enabled the Cathy agent to *recognise* Joe.\n", + "\n", + "Where the transform used above becomes essential is in a Group Chat using the `auto` selection mode (default), when the Group Chat Manager is selecting an agent based on their `name`.\n", + "\n", + "## Transforms in group chats\n", + "\n", + "As noted above, it is important when using non-OpenAI models to inject the agent's name into the messages when you are using `auto` agent selection mode. By doing so, you are giving the model the best chance at understanding which agent belongs to each message.\n", + "\n", + "Additionally, group chats can involve a large number of messages and, therefore, tokens. So, to assist with keeping the context used within your model's context window you can use a compression transform.\n", + "\n", + "Below is a group chat example that incorporates these two transforms and relies on the LLM using agent names to determine and select the next agent.\n", + "\n", + "We'll use Anthropic's Claude 3.5 Sonnet config from the previous example as the LLM for all agents and the group chat manager (which selects the next agent to speak).\n", + "\n", + "The scenario in the example is the production of two kid-friendly long-form articles on cloud formations.\n", + "\n", + "Let's start by creating our three team members:\n", + "\n", + "- **Subject_Expert** will select a cloud formation and provide some bullet points about it.\n", + "- **Writer** will write long-form content, about 2,000 words, for the selected cloud formation based on the bullet points.\n", + "- **Scheduler** is responsible for delivering the task to the group chat manager, determining if we need to continue writing more articles, and to terminate the group chat by saying 'TERMINATE'." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [], + "source": [ + "sme_agent = ConversableAgent(\n", + " \"Subject_Expert\",\n", + " system_message=\"You're a subject matter expert on cloud formations and work in a team with a scheduler and a writer. Every time you're asked to speak it's for a new article. You must prepare for a new article by selecting a the cloud formation, providing a summary of that formation and the impact on weather, in bullet points. Make it kid friendly. Aim for a dozen bullet points. Your task is only to provide topics and bullet points on new articles, don't review any previously written articles.\",\n", + " description=\"An expert on cloud formations, great at developing ideas to write about.\",\n", + " llm_config={\"config_list\": config_list_claude},\n", + " human_input_mode=\"NEVER\",\n", + ")\n", + "\n", + "scheduler = ConversableAgent(\n", + " \"Scheduler\",\n", + " system_message=\"You're a marketing expert, managing the production of a specific number of articles. Count the number of articles written and once they have been written say the word 'TERMINATE'.\",\n", + " description=\"A marketing expert that's excellent at managing the production of articles.\",\n", + " llm_config={\"config_list\": config_list_claude},\n", + " human_input_mode=\"NEVER\",\n", + ")\n", + "\n", + "writer = ConversableAgent(\n", + " \"Writer\",\n", + " system_message=\"You're a writer of online news articles on scientific topics, written for an audience of primary school students. Aim for 2,000 words for each article.\",\n", + " description=\"An excellent writer, takes given topics and writes long-form articles.\",\n", + " llm_config={\"config_list\": config_list_claude},\n", + " human_input_mode=\"NEVER\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we create our two transforms, one for injecting the `name` and the other to compress the messages if the estimated token count for all messages combined is greater than 1,000 tokens.\n", + "\n", + "As these transforms will be applied to the nested chat in a group chat where the next speaker is selected, we add a filter to the transforms to not apply to `system` messages and to messages from the `checking_agent` who is the agent within the nested chat for selecting the next speaker.\n", + "\n", + "These exclusions are used to minimise any loss of instruction in those messages as they are critical for speaker selection." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [], + "source": [ + "from autogen.agentchat.contrib.capabilities.text_compressors import LLMLingua\n", + "from autogen.agentchat.contrib.capabilities.transforms import TextMessageCompressor, TextMessageContentName\n", + "\n", + "# Create transform to inject name\n", + "# This will inject the agent's name for a message into the start of the message content.\n", + "# E.g. \"'Subject_Expert' said\\n...\"\n", + "name_transform = TextMessageContentName(\n", + " position=\"start\",\n", + " format_string=\"'{name}' said:\\n\",\n", + " filter_dict={\n", + " \"role\": [\"system\"],\n", + " \"name\": [\"checking_agent\"],\n", + " }, # don't add the name for the select speaker-specific nested-chat agents\n", + ")\n", + "\n", + "# Create transform to compress messages\n", + "# If you don't have LLMLingua installed: pip install LLMLingua\n", + "llm_lingua = LLMLingua()\n", + "compress_transform = TextMessageCompressor(\n", + " text_compressor=llm_lingua,\n", + " min_tokens=1000, # Don't compress if total tokens in list of messages is <= 1000\n", + " filter_dict={\n", + " \"role\": [\"system\"],\n", + " \"name\": [\"checking_agent\"],\n", + " }, # don't compress messages specifically for the select speaker prompts\n", + ")\n", + "\n", + "# Create the TransformMessages that will be applied\n", + "# In this case we are only putting in one transform but you could\n", + "# stack the transforms if you also wanted to do others, like\n", + "# compressing the text. Transforms are performed sequentially.\n", + "select_speaker_transforms = transform_messages.TransformMessages(\n", + " transforms=[\n", + " compress_transform,\n", + " name_transform,\n", + " ]\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With transforms created, we can apply them to the group chat's select speaker nested chat.\n", + "\n", + "In addition to the application of the transforms to the group chat's `select_speaker_transform_messages` parameter, we are providing explicit instructions on the order of agents within the `select_speaker_message_template`." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [], + "source": [ + "from autogen import GroupChat, GroupChatManager\n", + "\n", + "group_chat = GroupChat(\n", + " agents=[sme_agent, scheduler, writer],\n", + " messages=[],\n", + " max_round=10,\n", + " select_speaker_message_template=\"\"\"You manage a team that produces and releases articles.\n", + " The roles available in the team are:\n", + " {roles}\n", + " Take the task given and coordinate the production of one or more articles.\n", + " The order for each article should be the Subject_Expert first, then the Writer to write an article, then the Scheduler to review and determine if more are required.\n", + " Finally, you can output the word 'TERMINATE' to signify the end of the task.\"\"\",\n", + " select_speaker_prompt_template=\"Read the above conversation, select the next person from {agentlist} and only return the role.\",\n", + " # Transforms applied to the group chat speaker selection when in 'auto' mode\n", + " select_speaker_transform_messages=select_speaker_transforms,\n", + " select_speaker_auto_verbose=True, # See the selection process\n", + ")\n", + "\n", + "manager = GroupChatManager(\n", + " groupchat=group_chat,\n", + " llm_config={\"config_list\": config_list_claude},\n", + " is_termination_msg=lambda x: \"TERMINATE\" in x.get(\"content\", \"\"),\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will also apply the transforms to each of the agents so that when they are getting the messages to respond to they are compressed and have the names of the agents injected." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [], + "source": [ + "# We add the transforms to the team of agents so they understand who has said what and the messages are compressed to save tokens\n", + "select_speaker_transforms.add_to_agent(sme_agent)\n", + "select_speaker_transforms.add_to_agent(scheduler)\n", + "select_speaker_transforms.add_to_agent(writer)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Start the chat and show the cost at the end.\n", + "\n", + "Note: `select_speaker_auto_verbose` was set to True on the group chat so you can see the speaker selection process in between each message." + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mScheduler\u001b[0m (to chat_manager):\n", + "\n", + "We need a couple of articles on different cloud formations, let's get some help on creating them!\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mchecking_agent\u001b[0m (to speaker_selection_agent):\n", + "\n", + "Read the above conversation, select the next person from ['Subject_Expert', 'Scheduler', 'Writer'] and only return the role.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33m1 message(s) changed to incorporate name.\u001b[0m\n", + "\u001b[33mspeaker_selection_agent\u001b[0m (to checking_agent):\n", + "\n", + "Subject_Expert\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[32m>>>>>>>> Select speaker attempt 1 of 3 successfully selected: Subject_Expert\u001b[0m\n", + "\u001b[32m\n", + "Next speaker: Subject_Expert\n", + "\u001b[0m\n", + "\u001b[33m1 message(s) changed to incorporate name.\u001b[0m\n", + "\u001b[33mSubject_Expert\u001b[0m (to chat_manager):\n", + "\n", + "Certainly! I'd be happy to provide topics and bullet points for an article on a specific cloud formation. Let's start with one of the most recognizable cloud types: Cumulus clouds.\n", + "\n", + "Cloud Formation: Cumulus Clouds\n", + "\n", + "Summary:\n", + "Cumulus clouds are puffy, white clouds that look like floating cotton balls in the sky. They're often associated with fair weather and are a common sight on sunny days.\n", + "\n", + "Bullet points for a kid-friendly article:\n", + "\n", + "β€’ Cumulus clouds are nicknamed \"fair weather clouds\"\n", + "β€’ They look like fluffy white cotton balls or popcorn in the sky\n", + "β€’ Cumulus clouds usually have flat bottoms and rounded tops\n", + "β€’ They form when warm air rises from the ground and cools as it goes up\n", + "β€’ These clouds are made of tiny water droplets\n", + "β€’ Cumulus clouds typically appear on sunny, pleasant days\n", + "β€’ They often form in the late morning and disappear by evening\n", + "β€’ Sometimes cumulus clouds can grow into big thunderstorms\n", + "β€’ Pilots of small planes often avoid flying through cumulus clouds because they can be bumpy\n", + "β€’ Cumulus clouds can cast fun shadows on the ground\n", + "β€’ They're great for cloud-watching and imagining shapes (like animals or objects)\n", + "β€’ Artists often include cumulus clouds in their paintings of sunny landscapes\n", + "\n", + "This information should provide a good starting point for an engaging, kid-friendly article about cumulus clouds. Let me know if you'd like bullet points on another cloud formation for the second article!\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mchecking_agent\u001b[0m (to speaker_selection_agent):\n", + "\n", + "Read the above conversation, select the next person from ['Subject_Expert', 'Scheduler', 'Writer'] and only return the role.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33m2 message(s) changed to incorporate name.\u001b[0m\n", + "\u001b[33mspeaker_selection_agent\u001b[0m (to checking_agent):\n", + "\n", + "Writer\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[32m>>>>>>>> Select speaker attempt 1 of 3 successfully selected: Writer\u001b[0m\n", + "\u001b[32m\n", + "Next speaker: Writer\n", + "\u001b[0m\n", + "\u001b[33m2 message(s) changed to incorporate name.\u001b[0m\n", + "\u001b[33mWriter\u001b[0m (to chat_manager):\n", + "\n", + "Thank you for the excellent outline on cumulus clouds. I'll use this information to create a 2000-word article for primary school students on cumulus clouds. Here's the article:\n", + "\n", + "Fluffy Friends in the Sky: All About Cumulus Clouds\n", + "\n", + "Have you ever looked up at the sky on a beautiful sunny day and seen big, puffy white clouds that look like giant cotton balls floating high above? Those clouds have a special name – they're called cumulus clouds! In this article, we're going to learn all about these fascinating clouds that paint our skies with their fluffy shapes. So, grab your imagination, and let's go on a cloud adventure!\n", + "\n", + "What Are Cumulus Clouds?\n", + "\n", + "Cumulus clouds are one of the most common and easily recognizable types of clouds in our sky. Their name comes from the Latin word \"cumulus,\" which means \"heap\" or \"pile.\" This makes sense because these clouds look like big heaps of cotton or fluffy piles of popcorn floating in the air.\n", + "\n", + "These clouds are famous for their distinct shape. They have flat bottoms and rounded, puffy tops that often look like they're bubbling up into the sky. Cumulus clouds are usually bright white, especially on the parts that face the sun. Sometimes, the bottom parts of the clouds can look a bit grey, especially if they're very big.\n", + "\n", + "Cumulus clouds are so well-known and loved that they've earned a special nickname. People often call them \"fair weather clouds\" because they're usually seen on nice, sunny days when the weather is pleasant. When you see cumulus clouds in the sky, it usually means it's a great day to play outside!\n", + "\n", + "How Do Cumulus Clouds Form?\n", + "\n", + "Now that we know what cumulus clouds look like, let's explore how these fluffy sky friends come to be. The process of cumulus cloud formation is quite interesting and involves some cool science!\n", + "\n", + "It all starts with the sun warming up the ground. As the ground gets warm, it heats the air right above it. This warm air starts to rise because hot air is lighter than cold air. As the warm air goes up, it begins to cool down. This is because the higher you go in the atmosphere, the colder it gets.\n", + "\n", + "When the rising warm air cools down enough, something magical happens. The water vapor (tiny bits of water that float in the air) in this cooling air starts to condense. Condensation is when water vapor turns back into liquid water. This creates tiny water droplets that clump together to form the cloud.\n", + "\n", + "The process doesn't stop there! As long as there's warm air rising from below, the cloud keeps growing bigger and puffier. That's why cumulus clouds often look like they're bubbling or boiling at the top. They're constantly growing and changing shape as more warm air rises and more water droplets form.\n", + "\n", + "Interestingly, even though cumulus clouds look super fluffy and solid, they're actually made up of millions of tiny water droplets floating in the air. If you could touch a cumulus cloud (which, unfortunately, you can't), it would feel more like fog than a fluffy pillow!\n", + "\n", + "When Can We See Cumulus Clouds?\n", + "\n", + "One of the cool things about cumulus clouds is that they follow a bit of a daily schedule. They're like nature's clock in the sky!\n", + "\n", + "Cumulus clouds typically start to form in the late morning. This is when the sun has had enough time to warm up the ground and get those pockets of warm air rising. As the day goes on and gets warmer, you might see more and more cumulus clouds popping up in the sky.\n", + "\n", + "These clouds usually reach their peak in the afternoon when the day is at its warmest. This is the best time for cloud watching! You might see lots of different shapes and sizes of cumulus clouds dotting the blue sky.\n", + "\n", + "As evening approaches and the air starts to cool down, cumulus clouds often begin to disappear. Without the warm rising air to keep them growing, these clouds tend to evaporate and fade away. By nighttime, the sky is often clear again, ready for stars to twinkle.\n", + "\n", + "Remember, though, that weather can be unpredictable. Sometimes cumulus clouds stick around longer, and sometimes they might not form at all. It all depends on the conditions in the atmosphere that day.\n", + "\n", + "Cumulus Clouds and Weather\n", + "\n", + "While cumulus clouds are often called \"fair weather clouds,\" they can actually tell us a lot about what's happening in the atmosphere and what kind of weather we might expect.\n", + "\n", + "On most days, seeing cumulus clouds means the weather is likely to stay nice. These small to medium-sized puffy clouds usually indicate stable air and good weather conditions. They're the kind of clouds you want to see on a picnic day or when you're heading to the beach!\n", + "\n", + "However, cumulus clouds can sometimes grow into much larger clouds called cumulonimbus clouds. These are the big, tall clouds that can bring thunderstorms. If you see cumulus clouds starting to grow very tall and dark at the bottom, it might be a sign that a storm is brewing.\n", + "\n", + "Pilots of small airplanes often try to avoid flying through cumulus clouds. Even though these clouds look soft and fluffy from the ground, they can create bumpy air currents that can make for an uncomfortable flight. Big cumulus clouds can also have strong up and down air movements inside them, which can be challenging for small aircraft to navigate.\n", + "\n", + "Fun with Cumulus Clouds\n", + "\n", + "Cumulus clouds aren't just interesting to learn about – they're also great for having fun! Here are some enjoyable activities you can do with cumulus clouds:\n", + "\n", + "1. Cloud Watching: On a day with lots of cumulus clouds, lie down on the grass and look up at the sky. Let your imagination run wild! What shapes can you see in the clouds? Maybe you'll spot a rabbit, a dragon, or even a sailing ship! Cloud watching is a great way to relax and be creative.\n", + "\n", + "2. Cloud Shadows: On sunny days with scattered cumulus clouds, watch how the clouds cast shadows on the ground. These shadows move as the clouds drift across the sky, creating a constantly changing pattern on the earth below.\n", + "\n", + "3. Cloud Photography: If you have a camera or a smartphone, try taking pictures of cumulus clouds. You might capture some really interesting shapes or beautiful scenes of clouds against a blue sky.\n", + "\n", + "4. Cloud Diary: Keep a cloud diary for a week or a month. Each day, look out the window and draw or describe the clouds you see. Over time, you'll start to notice patterns in how the clouds change with the weather.\n", + "\n", + "5. Cloud in a Jar Experiment: With an adult's help, you can even make your own cumulus cloud in a jar! This fun science experiment helps you understand how these clouds form.\n", + "\n", + "Cumulus Clouds in Art and Culture\n", + "\n", + "Cumulus clouds are so beautiful and recognizable that they've become a popular subject in art and culture.\n", + "\n", + "Many famous painters have included cumulus clouds in their landscapes. Artists like Vincent van Gogh and Claude Monet often painted scenes with big, puffy clouds floating over fields or water. These clouds add depth and movement to paintings, making the scenes feel alive and dynamic.\n", + "\n", + "In children's books and cartoons, cumulus clouds are often drawn as simple white puffs. They're used to show that it's a nice day in the story. Sometimes, characters in cartoons even sit or sleep on these fluffy clouds (even though in real life, you'd fall right through a cloud!).\n", + "\n", + "Cumulus clouds have also inspired many writers and poets. Their ever-changing shapes and the way they float peacefully across the sky have been described in countless poems and stories.\n", + "\n", + "In some cultures, cumulus clouds have even played a role in weather folklore. For example, there's an old saying: \"If clouds are puffy like cotton on high, then dry weather's coming by and by.\" This shows how people have long used cumulus clouds as a way to predict the weather.\n", + "\n", + "The Science Behind Cumulus Clouds\n", + "\n", + "While cumulus clouds might look simple, there's actually a lot of complex science happening inside them. Here are some fascinating scientific facts about cumulus clouds:\n", + "\n", + "1. Temperature: The temperature inside a cumulus cloud is usually below freezing, even on a warm day! This is because the cloud is high up in the atmosphere where it's much colder.\n", + "\n", + "2. Weight: Even though cumulus clouds look light and fluffy, they're actually quite heavy. An average cumulus cloud can weigh as much as 100 elephants! But don't worry – they stay up in the sky because the tiny water droplets are spread out over a large area.\n", + "\n", + "3. Movement: Cumulus clouds move with the wind. The speed at which they move can tell us how fast the wind is blowing high up in the atmosphere.\n", + "\n", + "4. Life Span: Most cumulus clouds only last for about 15 to 20 minutes before they evaporate. However, in the right conditions, they can last for hours, constantly reforming as they move across the sky.\n", + "\n", + "5. Size: While most cumulus clouds are fairly small, some can grow to be enormous. The biggest cumulus clouds can be as tall as a 10-story building!\n", + "\n", + "Conclusion\n", + "\n", + "Cumulus clouds are truly amazing parts of our sky. From their fluffy, cotton-like appearance to the complex science behind their formation, these clouds have so much to teach us about weather and the atmosphere.\n", + "\n", + "Next time you're outside on a nice day, take a moment to look up at the sky. If you see those familiar puffy white clouds, you'll know you're looking at cumulus clouds. Remember how they form, think about the tiny water droplets that make them up, and let your imagination run wild with the shapes you see.\n", + "\n", + "Cumulus clouds are more than just beautiful sights in the sky. They're a reminder of the constant changes happening in our atmosphere, the amazing processes of nature, and the wonder of the world around us. So keep your eyes on the skies, and enjoy the fluffy, fascinating world of cumulus clouds!\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mchecking_agent\u001b[0m (to speaker_selection_agent):\n", + "\n", + "Read the above conversation, select the next person from ['Subject_Expert', 'Scheduler', 'Writer'] and only return the role.\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "Token indices sequence length is longer than the specified maximum sequence length for this model (2324 > 512). Running this sequence through the model will result in indexing errors\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33m1282 tokens saved with text compression.\u001b[0m\n", + "\u001b[33m3 message(s) changed to incorporate name.\u001b[0m\n", + "\u001b[33mspeaker_selection_agent\u001b[0m (to checking_agent):\n", + "\n", + "Scheduler\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[32m>>>>>>>> Select speaker attempt 1 of 3 successfully selected: Scheduler\u001b[0m\n", + "\u001b[32m\n", + "Next speaker: Scheduler\n", + "\u001b[0m\n", + "\u001b[33m1282 tokens saved with text compression.\u001b[0m\n", + "\u001b[33m3 message(s) changed to incorporate name.\u001b[0m\n", + "\u001b[33mScheduler\u001b[0m (to chat_manager):\n", + "\n", + "Thank you for providing that detailed article on cumulus clouds. It's a well-written piece that covers the topic thoroughly for primary school students. Since we now have one complete article, I'll count that as the first one written.\n", + "\n", + "Articles written: 1\n", + "\n", + "Please continue with the next article or task.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mchecking_agent\u001b[0m (to speaker_selection_agent):\n", + "\n", + "Read the above conversation, select the next person from ['Subject_Expert', 'Scheduler', 'Writer'] and only return the role.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33m1311 tokens saved with text compression.\u001b[0m\n", + "\u001b[33m4 message(s) changed to incorporate name.\u001b[0m\n", + "\u001b[33mspeaker_selection_agent\u001b[0m (to checking_agent):\n", + "\n", + "Subject_Expert\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[32m>>>>>>>> Select speaker attempt 1 of 3 successfully selected: Subject_Expert\u001b[0m\n", + "\u001b[32m\n", + "Next speaker: Subject_Expert\n", + "\u001b[0m\n", + "\u001b[33m1311 tokens saved with text compression.\u001b[0m\n", + "\u001b[33m4 message(s) changed to incorporate name.\u001b[0m\n", + "\u001b[33mSubject_Expert\u001b[0m (to chat_manager):\n", + "\n", + "'Subject_Expert' said:\n", + "Excellent, I'm glad the article on cumulus clouds was well-received. For our next article, let's focus on stratus clouds. Here's an outline with kid-friendly bullet points on stratus clouds:\n", + "\n", + "Cloud Formation: Stratus Clouds\n", + "\n", + "β€’ Stratus clouds are low-lying, gray clouds that often cover the entire sky\n", + "β€’ They look like a big, gray blanket stretched across the sky\n", + "β€’ The word \"stratus\" comes from the Latin word for \"layer\" or \"spread out\"\n", + "β€’ These clouds usually form less than 6,000 feet (1,800 meters) above the ground\n", + "β€’ Stratus clouds often bring light rain or drizzle, but not heavy downpours\n", + "β€’ Sometimes, stratus clouds can touch the ground - we call this fog!\n", + "β€’ They form when a large area of warm air rises slowly and cools down\n", + "β€’ Stratus clouds can make the day look gloomy and overcast\n", + "β€’ They don't have distinct shapes like cumulus clouds do\n", + "β€’ These clouds can stick around for hours or even days\n", + "β€’ Stratus clouds are common in cool, damp climates\n", + "β€’ They often form in the morning and can clear up by afternoon\n", + "β€’ Pilots need to be careful when flying through stratus clouds because they can reduce visibility\n", + "\n", + "This outline provides a good starting point for an engaging article about stratus clouds, suitable for primary school students. It covers the basic characteristics, formation, and impact of stratus clouds on weather and daily life.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mchecking_agent\u001b[0m (to speaker_selection_agent):\n", + "\n", + "Read the above conversation, select the next person from ['Subject_Expert', 'Scheduler', 'Writer'] and only return the role.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33m1475 tokens saved with text compression.\u001b[0m\n", + "\u001b[33m5 message(s) changed to incorporate name.\u001b[0m\n", + "\u001b[33mspeaker_selection_agent\u001b[0m (to checking_agent):\n", + "\n", + "Writer\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[32m>>>>>>>> Select speaker attempt 1 of 3 successfully selected: Writer\u001b[0m\n", + "\u001b[32m\n", + "Next speaker: Writer\n", + "\u001b[0m\n", + "\u001b[33m1475 tokens saved with text compression.\u001b[0m\n", + "\u001b[33m5 message(s) changed to incorporate name.\u001b[0m\n", + "\u001b[33mWriter\u001b[0m (to chat_manager):\n", + "\n", + "'Writer' said:\n", + "Thank you for the excellent outline on stratus clouds. I'll use this information to create a 2000-word article for primary school students about stratus clouds. Here's the article:\n", + "\n", + "The Gray Blanket in the Sky: All About Stratus Clouds\n", + "\n", + "Have you ever woken up to a gloomy day where the whole sky looks like one big gray blanket? Those aren't just any clouds you're seeing – they're called stratus clouds! Let's embark on a cloudy adventure to learn all about these fascinating sky coverings that can make the world look a bit dreary but play an important role in our weather.\n", + "\n", + "What are Stratus Clouds?\n", + "\n", + "Stratus clouds are low-lying, gray clouds that often cover the entire sky. Their name comes from the Latin word \"stratus,\" which means \"layer\" or \"spread out.\" That's exactly what these clouds do – they spread out across the sky like a huge, fluffy gray blanket!\n", + "\n", + "Unlike their puffy cousins, the cumulus clouds, stratus clouds don't have distinct shapes. You won't see any fun animals or objects in stratus clouds. Instead, they create a uniform, featureless layer that can stretch as far as the eye can see.\n", + "\n", + "Stratus clouds are low clouds, which means they form pretty close to the ground. In fact, they usually hang out less than 6,000 feet (or about 1,800 meters) above the Earth's surface. Sometimes, they can be so low that they touch the ground – when this happens, we call it fog!\n", + "\n", + "How Do Stratus Clouds Form?\n", + "\n", + "The formation of stratus clouds is an interesting process that involves some cool science. Here's how it happens:\n", + "\n", + "1. Warm Air Rises: Just like with other cloud types, it all starts with warm air rising from the Earth's surface.\n", + "\n", + "2. Cooling Process: As this warm air rises, it starts to cool down. Remember, the higher you go in the atmosphere, the colder it gets!\n", + "\n", + "3. Water Vapor Condenses: The cooling air can't hold as much water vapor (invisible water in the air) as warm air can. So, as the air cools, the water vapor starts to condense, turning into tiny water droplets.\n", + "\n", + "4. Cloud Formation: These tiny water droplets clump together to form the cloud. In the case of stratus clouds, this happens in a wide, flat layer.\n", + "\n", + "5. Stable Atmosphere: Stratus clouds often form when the atmosphere is stable, meaning there's not much mixing between different layers of air. This allows the cloud to spread out in a smooth, even layer.\n", + "\n", + "Stratus clouds can form in a couple of different ways:\n", + "\n", + "β€’ When a layer of warm air moves over a cooler surface (like when warm air moves over a cool ocean).\n", + "β€’ When snow or rain evaporates as it falls through dry air near the ground, cooling and moistening that air until it becomes saturated and forms a cloud.\n", + "\n", + "Weather Associated with Stratus Clouds\n", + "\n", + "When you see stratus clouds, it's a good idea to grab a jacket or an umbrella! These clouds often bring gloomy, overcast weather. Here's what you can expect when stratus clouds are in the sky:\n", + "\n", + "1. Light Rain or Drizzle: Stratus clouds can produce light rain or drizzle. This isn't the heavy downpour you might see with thunderstorms, but more of a constant, gentle sprinkle.\n", + "\n", + "2. Fog: Remember how we said stratus clouds can touch the ground? When they do, we call it fog. So if you've ever walked through a foggy morning, you've actually been inside a stratus cloud!\n", + "\n", + "3. Cool Temperatures: Because stratus clouds block out the sun, days with these clouds tend to be cooler than clear, sunny days.\n", + "\n", + "4. Low Visibility: The thick layer of stratus clouds can make it hard to see very far, especially if they're low to the ground or if it's foggy.\n", + "\n", + "5. Long-lasting: Unlike some other cloud types that come and go quickly, stratus clouds can stick around for hours or even days, especially in cool, damp climates.\n", + "\n", + "Stratus Clouds Around the World\n", + "\n", + "Stratus clouds are common in many parts of the world, but they're especially frequent in certain areas:\n", + "\n", + "β€’ Coastal Regions: Places near the ocean often see a lot of stratus clouds, especially in the morning. The cool water can cause warm air to cool and form these layered clouds.\n", + "\n", + "β€’ Cool, Damp Climates: Areas with cool, moist weather (like the Pacific Northwest in the United States or parts of the United Kingdom) frequently have stratus cloud cover.\n", + "\n", + "β€’ Arctic and Antarctic Regions: The cold polar regions often have stratus clouds, contributing to their characteristically gray skies.\n", + "\n", + "Stratus Clouds and Daily Life\n", + "\n", + "Stratus clouds might not be as fun to look at as puffy cumulus clouds, but they still have a big impact on our daily lives:\n", + "\n", + "1. Agriculture: Farmers pay attention to stratus clouds because they can bring needed moisture for crops, but too many cloudy days can reduce sunlight for plant growth.\n", + "\n", + "2. Solar Power: Stratus clouds can reduce the effectiveness of solar panels by blocking out sunlight.\n", + "\n", + "3. Aviation: Pilots need to be very careful when flying through stratus clouds because they can greatly reduce visibility.\n", + "\n", + "4. Mood: Some people find that long periods of stratus cloud cover can affect their mood, making them feel a bit gloomy. That's why sunny days feel so good after a long stretch of cloudy weather!\n", + "\n", + "5. Photography: While stratus clouds might not make for the most exciting cloud photos, they can create a soft, even light that photographers sometimes prefer for certain types of pictures.\n", + "\n", + "Fun Facts About Stratus Clouds\n", + "\n", + "Let's explore some interesting tidbits about these gray sky blankets:\n", + "\n", + "1. Cloud Seeding: Sometimes, scientists try to make it rain by \"seeding\" stratus clouds. They drop tiny particles into the clouds to help water droplets form and fall as rain.\n", + "\n", + "2. Natural Air Conditioning: Stratus clouds act like nature's air conditioning, keeping the Earth cool by reflecting sunlight back into space.\n", + "\n", + "3. Cloud Forests: In some mountainous tropical areas, stratus clouds constantly cover the forest, creating a unique ecosystem called a \"cloud forest.\"\n", + "\n", + "4. Noctilucent Clouds: The highest clouds in Earth's atmosphere, called noctilucent clouds, are a special type of stratus cloud that forms in the mesosphere, about 50 miles (80 km) above the Earth's surface!\n", + "\n", + "5. Cloud Naming: Stratus clouds are part of the ten basic cloud types identified by Luke Howard in 1803. He's known as the \"father of meteorology\" for his work in classifying clouds.\n", + "\n", + "Stratus Cloud Variations\n", + "\n", + "While stratus clouds are generally uniform layers, there are a few variations:\n", + "\n", + "1. Stratus Nebulosus: This is the classic, featureless gray layer we typically think of as stratus clouds.\n", + "\n", + "2. Stratus Fractus: These are ragged, broken pieces of stratus clouds, often seen during or after rain.\n", + "\n", + "3. Altostratus: These are similar to stratus but form at higher altitudes, creating a thinner, more translucent layer.\n", + "\n", + "4. Nimbostratus: These are thick, dark stratus clouds that produce steady rain or snow.\n", + "\n", + "Activities to Learn About Stratus Clouds\n", + "\n", + "Even though stratus clouds aren't as visually exciting as some other cloud types, there are still fun ways to learn about them:\n", + "\n", + "1. Cloud Diary: Keep a cloud diary for a week or a month. Draw or describe the clouds you see each day, noting when you observe stratus clouds.\n", + "\n", + "2. Make a Cloud in a Jar: With an adult's help, you can create a miniature stratus cloud in a jar using hot water, hairspray, and ice.\n", + "\n", + "3. Fog Observation: On a foggy day, go outside (with an adult) and observe how it feels to be inside a stratus cloud that's touching the ground.\n", + "\n", + "4. Weather Station: Set up a simple weather station at home or school. Track temperature, humidity, and cloud cover, noting how these change when stratus clouds are present.\n", + "\n", + "5. Cloud Art: Create art inspired by stratus clouds. You could use gray paint or pencils to create the effect of a cloudy sky, or try making a collage using different shades of gray paper.\n", + "\n", + "Conclusion\n", + "\n", + "Stratus clouds might not be the most exciting clouds in the sky, but they're an important part of our weather and have a big impact on our daily lives. From creating gloomy, drizzly days to helping cool our planet, these low-lying gray blankets play a crucial role in Earth's atmosphere.\n", + "\n", + "Next time you wake up to a gray, overcast day, remember that you're looking at stratus clouds. Think about how they formed, what kind of weather they might bring, and how they're affecting the world around you. Even on a cloudy day, there's always something interesting to learn about the sky above us!\n", + "\n", + "So keep your eyes on the skies, young meteorologists, and enjoy exploring the fascinating world of stratus clouds!\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mchecking_agent\u001b[0m (to speaker_selection_agent):\n", + "\n", + "Read the above conversation, select the next person from ['Subject_Expert', 'Scheduler', 'Writer'] and only return the role.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33m2485 tokens saved with text compression.\u001b[0m\n", + "\u001b[33m6 message(s) changed to incorporate name.\u001b[0m\n", + "\u001b[33mspeaker_selection_agent\u001b[0m (to checking_agent):\n", + "\n", + "Please continue.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[31m>>>>>>>> Select speaker attempt #1 failed as it did not include any agent names.\u001b[0m\n", + "\u001b[33mchecking_agent\u001b[0m (to speaker_selection_agent):\n", + "\n", + "You didn't choose a speaker. As a reminder, to determine the speaker use these prioritised rules:\n", + " 1. If the context refers to themselves as a speaker e.g. \"As the...\" , choose that speaker's name\n", + " 2. If it refers to the \"next\" speaker name, choose that name\n", + " 3. Otherwise, choose the first provided speaker's name in the context\n", + " The names are case-sensitive and should not be abbreviated or changed.\n", + " The only names that are accepted are ['Subject_Expert', 'Scheduler', 'Writer'].\n", + " Respond with ONLY the name of the speaker and DO NOT provide a reason.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33m2487 tokens saved with text compression.\u001b[0m\n", + "\u001b[33m7 message(s) changed to incorporate name.\u001b[0m\n", + "\u001b[33mspeaker_selection_agent\u001b[0m (to checking_agent):\n", + "\n", + "Scheduler\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[32m>>>>>>>> Select speaker attempt 2 of 3 successfully selected: Scheduler\u001b[0m\n", + "\u001b[32m\n", + "Next speaker: Scheduler\n", + "\u001b[0m\n", + "\u001b[33m2485 tokens saved with text compression.\u001b[0m\n", + "\u001b[33m6 message(s) changed to incorporate name.\u001b[0m\n", + "\u001b[33mScheduler\u001b[0m (to chat_manager):\n", + "\n", + "'Scheduler' said:\n", + "Excellent work! You've now completed two well-written articles: one on cumulus clouds and another on stratus clouds. Both are informative and engaging for primary school students.\n", + "\n", + "Articles written: 2\n", + "\n", + "Since we've reached our target number of articles, I'll now say: TERMINATE\n", + "\n", + "--------------------------------------------------------------------------------\n", + "The cost of the chat was:\n", + "{'usage_including_cached_inference': {'total_cost': 0.013266, 'claude-3-5-sonnet-20240620': {'cost': 0.013266, 'prompt_tokens': 3732, 'completion_tokens': 138, 'total_tokens': 3870}}, 'usage_excluding_cached_inference': {'total_cost': 0.013266, 'claude-3-5-sonnet-20240620': {'cost': 0.013266, 'prompt_tokens': 3732, 'completion_tokens': 138, 'total_tokens': 3870}}}\n" + ] + } + ], + "source": [ + "chat_result = scheduler.initiate_chat(\n", + " recipient=manager,\n", + " message=\"We need a couple of articles on different cloud formations, let's get some help on creating them!\",\n", + ")\n", + "\n", + "print(f\"The cost of the chat was:\\n{chat_result.cost}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "There's a lot to digest in the above code, let's break it down.\n", + "\n", + "1. It successfully did the task, getting two articles written (Yay!)\n", + "2. The sequence of agents was selected successfully: Scheduler to Subject_Expert to Writer then back to Scheduler to Subject_Expert to Writer to Scheduler who then terminates\n", + "3. Transform messages show the number of messages incorporating the name and the tokens saved during the process\n", + "4. We occasionally see the next agent name \"Please continue.\" being proposed by the LLM and this is because these continuation messages are inter-woven in the messages sent to Anthropic's API. This is handled well with the select speaker retries, but further prompt tuning could help eliminate these anomalies\n", + "\n", + "Additional notes:\n", + "\n", + "- Would this have worked without the transforms? Taking out the transforms resulted in a run producing the following incorrect sequence: Scheduler to Subject_Export to Writer back to Writer then to Scheduler.\n", + "- Tweaking - tweaking the system messages and descriptions for agents and the group chat select speaker nested chat also played a large role in steering the LLM to the correct output. A combination of prompt engineering and the transforms may be required to achieve consistent results.\n", + "\n" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "autogen", + "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.11.9" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} From f44309bc00c5a20bf450f540d56bb11b4ad069ee Mon Sep 17 00:00:00 2001 From: Kirushikesh DB <49152921+Kirushikesh@users.noreply.github.com> Date: Mon, 26 Aug 2024 14:01:40 +0530 Subject: [PATCH 16/16] Update gallery.json (#3414) Co-authored-by: Li Jiang Co-authored-by: gagb --- website/src/data/gallery.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/website/src/data/gallery.json b/website/src/data/gallery.json index 10ed9f6866db..02e9e5cdd66d 100644 --- a/website/src/data/gallery.json +++ b/website/src/data/gallery.json @@ -228,5 +228,12 @@ "tags": [ "tools", "ui", "app" ] + }, + { + "title": "Expense Tracker - using Autogen", + "link": "https://github.com/Kirushikesh/Personal-Finance-Agent", + "description": "Tracks personal finance income and expenses then helps the user to analyse it using AutoGen agents.", + "image": "default.png", + "tags": ["tools", "app"] } ]