diff --git a/autogen/agentchat/groupchat.py b/autogen/agentchat/groupchat.py index 69d318eebbdd..09a34645f8e7 100644 --- a/autogen/agentchat/groupchat.py +++ b/autogen/agentchat/groupchat.py @@ -18,9 +18,9 @@ from ..formatting_utils import colored from ..graph_utils import check_graph_validity, invert_disallowed_to_allowed from ..io.base import IOStream +from ..oai.client import ModelClient from ..runtime_logging import log_new_agent, logging_enabled from .agent import Agent -from .chat import ChatResult from .contrib.capabilities import transform_messages from .conversable_agent import ConversableAgent @@ -106,6 +106,8 @@ def custom_speaker_selection_func( "clear history" phrase in user prompt. This is experimental feature. See description of GroupChatManager.clear_agents_history function for more info. - send_introductions: send a round of introductions at the start of the group chat, so agents know who they can speak to (default: False) + - select_speaker_auto_model_client_cls: Custom model client class for the internal speaker select agent used during 'auto' speaker selection (optional) + - select_speaker_auto_llm_config: LLM config for the internal speaker select agent used during 'auto' speaker selection (optional) - role_for_select_speaker_messages: sets the role name for speaker selection when in 'auto' mode, typically 'user' or 'system'. (default: 'system') """ @@ -143,6 +145,8 @@ def custom_speaker_selection_func( Respond with ONLY the name of the speaker and DO NOT provide a reason.""" select_speaker_transform_messages: Optional[transform_messages.TransformMessages] = None select_speaker_auto_verbose: Optional[bool] = False + select_speaker_auto_model_client_cls: Optional[Union[ModelClient, List[ModelClient]]] = None + select_speaker_auto_llm_config: Optional[Union[Dict, Literal[False]]] = None role_for_select_speaker_messages: Optional[str] = "system" _VALID_SPEAKER_SELECTION_METHODS = ["auto", "manual", "random", "round_robin"] @@ -587,6 +591,79 @@ def _finalize_speaker(self, last_speaker: Agent, final: bool, name: str, agents: agent = self.agent_by_name(name) return agent if agent else self.next_agent(last_speaker, agents) + def _register_client_from_config(self, agent: Agent, config: Dict): + model_client_cls_to_match = config.get("model_client_cls") + if model_client_cls_to_match: + if not self.select_speaker_auto_model_client_cls: + raise ValueError( + "A custom model was detected in the config but no 'model_client_cls' " + "was supplied for registration in GroupChat." + ) + + if isinstance(self.select_speaker_auto_model_client_cls, list): + # Register the first custom model client class matching the name specified in the config + matching_model_cls = [ + client_cls + for client_cls in self.select_speaker_auto_model_client_cls + if client_cls.__name__ == model_client_cls_to_match + ] + if len(set(matching_model_cls)) > 1: + raise RuntimeError( + f"More than one unique 'model_client_cls' with __name__ '{model_client_cls_to_match}'." + ) + if not matching_model_cls: + raise ValueError( + "No model's __name__ matches the model client class " + f"'{model_client_cls_to_match}' specified in select_speaker_auto_llm_config." + ) + select_speaker_auto_model_client_cls = matching_model_cls[0] + else: + # Register the only custom model client + select_speaker_auto_model_client_cls = self.select_speaker_auto_model_client_cls + + agent.register_model_client(select_speaker_auto_model_client_cls) + + def _register_custom_model_clients(self, agent: ConversableAgent): + if not self.select_speaker_auto_llm_config: + return + + config_format_is_list = "config_list" in self.select_speaker_auto_llm_config.keys() + if config_format_is_list: + for config in self.select_speaker_auto_llm_config["config_list"]: + self._register_client_from_config(agent, config) + elif not config_format_is_list: + self._register_client_from_config(agent, self.select_speaker_auto_llm_config) + + def _create_internal_agents( + self, agents, max_attempts, messages, validate_speaker_name, selector: Optional[ConversableAgent] = None + ): + checking_agent = ConversableAgent("checking_agent", default_auto_reply=max_attempts) + + # Register the speaker validation function with the checking agent + checking_agent.register_reply( + [ConversableAgent, None], + reply_func=validate_speaker_name, # Validate each response + remove_other_reply_funcs=True, + ) + + # Override the selector's config if one was passed as a parameter to this class + speaker_selection_llm_config = self.select_speaker_auto_llm_config or selector.llm_config + + # Agent for selecting a single agent name from the response + speaker_selection_agent = ConversableAgent( + "speaker_selection_agent", + system_message=self.select_speaker_msg(agents), + chat_messages={checking_agent: messages}, + llm_config=speaker_selection_llm_config, + human_input_mode="NEVER", + # Suppresses some extra terminal outputs, outputs will be handled by select_speaker_auto_verbose + ) + + # Register any custom model passed in select_speaker_auto_llm_config with the speaker_selection_agent + self._register_custom_model_clients(speaker_selection_agent) + + return checking_agent, speaker_selection_agent + def _auto_select_speaker( self, last_speaker: Agent, @@ -640,28 +717,8 @@ def validate_speaker_name(recipient, messages, sender, config) -> Tuple[bool, Un # Two-agent chat for speaker selection # Agent for checking the response from the speaker_select_agent - checking_agent = ConversableAgent("checking_agent", default_auto_reply=max_attempts) - - # Register the speaker validation function with the checking agent - checking_agent.register_reply( - [ConversableAgent, None], - reply_func=validate_speaker_name, # Validate each response - remove_other_reply_funcs=True, - ) - - # NOTE: Do we have a speaker prompt (select_speaker_prompt_template is not None)? If we don't, we need to feed in the last message to start the nested chat - - # Agent for selecting a single agent name from the response - speaker_selection_agent = ConversableAgent( - "speaker_selection_agent", - system_message=self.select_speaker_msg(agents), - chat_messages=( - {checking_agent: messages} - if self.select_speaker_prompt_template is not None - else {checking_agent: messages[:-1]} - ), - llm_config=selector.llm_config, - human_input_mode="NEVER", # Suppresses some extra terminal outputs, outputs will be handled by select_speaker_auto_verbose + checking_agent, speaker_selection_agent = self._create_internal_agents( + agents, max_attempts, messages, validate_speaker_name, selector ) # Create the starting message @@ -743,24 +800,8 @@ def validate_speaker_name(recipient, messages, sender, config) -> Tuple[bool, Un # Two-agent chat for speaker selection # Agent for checking the response from the speaker_select_agent - checking_agent = ConversableAgent("checking_agent", default_auto_reply=max_attempts) - - # Register the speaker validation function with the checking agent - checking_agent.register_reply( - [ConversableAgent, None], - reply_func=validate_speaker_name, # Validate each response - remove_other_reply_funcs=True, - ) - - # NOTE: Do we have a speaker prompt (select_speaker_prompt_template is not None)? If we don't, we need to feed in the last message to start the nested chat - - # Agent for selecting a single agent name from the response - speaker_selection_agent = ConversableAgent( - "speaker_selection_agent", - system_message=self.select_speaker_msg(agents), - chat_messages={checking_agent: messages}, - llm_config=selector.llm_config, - human_input_mode="NEVER", # Suppresses some extra terminal outputs, outputs will be handled by select_speaker_auto_verbose + checking_agent, speaker_selection_agent = self._create_internal_agents( + agents, max_attempts, messages, validate_speaker_name, selector ) # Create the starting message diff --git a/test/agentchat/test_groupchat.py b/test/agentchat/test_groupchat.py index 5efb839317cd..a9cb8091af7e 100755 --- a/test/agentchat/test_groupchat.py +++ b/test/agentchat/test_groupchat.py @@ -10,6 +10,7 @@ import io import json import logging +from types import SimpleNamespace from typing import Any, Dict, List, Optional from unittest import TestCase, mock @@ -2068,6 +2069,60 @@ def test_manager_resume_messages(): return_agent, return_message = manager.resume(messages="Let's get this conversation started.") +def test_custom_model_client(): + class CustomModelClient: + def __init__(self, config, **kwargs): + print(f"CustomModelClient config: {config}") + + def create(self, params): + num_of_responses = params.get("n", 1) + + response = SimpleNamespace() + response.choices = [] + response.model = "test_model_name" + + for _ in range(num_of_responses): + text = "this is a dummy text response" + choice = SimpleNamespace() + choice.message = SimpleNamespace() + choice.message.content = text + choice.message.function_call = None + response.choices.append(choice) + return response + + def message_retrieval(self, response): + choices = response.choices + return [choice.message.content for choice in choices] + + def cost(self, response) -> float: + response.cost = 0 + return 0 + + @staticmethod + def get_usage(response): + return {} + + llm_config = {"config_list": [{"model": "test_model_name", "model_client_cls": "CustomModelClient"}]} + + group_chat = autogen.GroupChat( + agents=[], + messages=[], + max_round=3, + select_speaker_auto_llm_config=llm_config, + select_speaker_auto_model_client_cls=CustomModelClient, + ) + + checking_agent, speaker_selection_agent = group_chat._create_internal_agents( + agents=[], messages=[], max_attempts=3, validate_speaker_name=(True, "test") + ) + + # Check that the custom model client is assigned to the speaker selection agent + assert isinstance(speaker_selection_agent.client._clients[0], CustomModelClient) + + # Check that the LLM Config is assigned + assert speaker_selection_agent.client._config_list == llm_config["config_list"] + + def test_select_speaker_transform_messages(): """Tests adding transform messages to a GroupChat for speaker selection when in 'auto' mode""" @@ -2182,5 +2237,6 @@ def test_manager_resume_message_assignment(): # test_manager_resume_returns() # test_manager_resume_messages() # test_select_speaker_transform_messages() - test_manager_resume_message_assignment() + # test_manager_resume_message_assignment() + test_custom_model_client() pass diff --git a/website/docs/topics/groupchat/using_custom_model_client_classes.ipynb b/website/docs/topics/groupchat/using_custom_model_client_classes.ipynb new file mode 100644 index 000000000000..a1cb5058f7c2 --- /dev/null +++ b/website/docs/topics/groupchat/using_custom_model_client_classes.ipynb @@ -0,0 +1,451 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Using Custom Model Client classes with Auto Speaker Selection\n", + "\n", + "````{=mdx}\n", + ":::tip\n", + "This documentation only applies when using the 'auto' speaker selection method for GroupChat **and** your GroupChatManager is using a Custom Model Client class.\n", + "\n", + "You don't need to change your GroupChat if either of these are the case:\n", + "- You are using a different speaker selection method, such as 'manual', 'random', 'round_robin', or a Callable\n", + "- Your GroupChatManager doesn't use a Custom Model Client class\n", + ":::\n", + "````\n", + "\n", + "During a group chat using the `auto` speaker selection method, an inner conversation between two agents is created to determine the next speaker after each turn. One of the speakers will take the `llm_config` from the `GroupChatManager` (the other inner agent doesn't use an `llm_config`).\n", + "\n", + "If the configuration for the GroupChatManager is using a Custom Model Client Class this is not propagated through to the inner conversation.\n", + "\n", + "So, you can control the configuration that the inner conversation agent uses by setting two properties on GroupChat:\n", + "\n", + "- **select_speaker_auto_llm_config**: Set this to your llm_config with the custom model client\n", + "- **select_speaker_auto_model_client_cls**: Set this to the class of your custom model client\n", + "\n", + "This control enables you to register the custom model client class for, or assign a completely different `llm_config` to, the inner conversation agent.\n", + "\n", + "See a simple example below.\n", + "\n", + "### Imports" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "from autogen.agentchat import ConversableAgent, GroupChat, GroupChatManager" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Sample Custom Model Client class\n", + "The class below is an example of a custom model client class that always returns the name `Alexandra`." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [], + "source": [ + "import random\n", + "from types import SimpleNamespace\n", + "\n", + "\n", + "class MyCustomModelClient:\n", + " def __init__(self, config, **kwargs):\n", + " print(f\"CustomModelClient config: {config}\")\n", + "\n", + " def create(self, params):\n", + " num_of_responses = params.get(\"n\", 1)\n", + "\n", + " response = SimpleNamespace()\n", + " response.choices = []\n", + " response.model = \"anything\"\n", + "\n", + " # Randomly choose between Alexandra, Mark, and Elizabeth as next speaker\n", + " agent_names = [\"Alexandra\", \"Mark\", \"Elizabeth\"]\n", + " random_index = random.randint(0, 2)\n", + "\n", + " for _ in range(num_of_responses):\n", + " text = f\"Randomly choosing... {agent_names[random_index]}\"\n", + " choice = SimpleNamespace()\n", + " choice.message = SimpleNamespace()\n", + " choice.message.content = text\n", + " choice.message.function_call = None\n", + " response.choices.append(choice)\n", + " return response\n", + "\n", + " def message_retrieval(self, response):\n", + " choices = response.choices\n", + " return [choice.message.content for choice in choices]\n", + "\n", + " def cost(self, response) -> float:\n", + " response.cost = 0\n", + " return 0\n", + "\n", + " @staticmethod\n", + " def get_usage(response):\n", + " return {}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### GroupChat with Custom Model Client class\n", + "Here we create `llm_config` that will use an actual LLM, then we create `custom_llm_config` that uses the custom model client class that we specified earlier.\n", + "\n", + "We add a few agents, all using the LLM-based configuration." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# Configuration for agents\n", + "llm_config = {\n", + " \"config_list\": [\n", + " {\n", + " \"api_type\": \"ollama\",\n", + " \"model\": \"llama3.1:8b\",\n", + " }\n", + " ]\n", + "}\n", + "\n", + "# Configuration for GroupChatManager\n", + "# using a Custom Model Client class (above)\n", + "custom_llm_config = {\n", + " \"config_list\": [\n", + " {\n", + " \"model_client_cls\": \"MyCustomModelClient\",\n", + " }\n", + " ]\n", + "}\n", + "\n", + "mark = ConversableAgent(\n", + " name=\"Mark\",\n", + " system_message=\"You are a customer who likes asking questions about accounting.\",\n", + " description=\"Customer who needs accounting help.\",\n", + " llm_config=llm_config,\n", + ")\n", + "\n", + "alexandra = ConversableAgent(\n", + " name=\"Alexandra\",\n", + " system_message=\"You are an accountant who provides detailed responses about accounting.\",\n", + " description=\"Accountant who loves to talk about accounting!\",\n", + " llm_config=llm_config,\n", + ")\n", + "\n", + "elizabeth = ConversableAgent(\n", + " name=\"Elizabeth\",\n", + " system_message=\"You are a head accountant who checks the answers of other accountants. Finish your response with the word 'BAZINGA'.\",\n", + " description=\"Head accountants, checks answers from accountants for validity.\",\n", + " llm_config=llm_config,\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we assign the `custom_llm_config` (which uses the custom model client class) and the custom model client class, `MyCustomModelClient`, to the GroupChat so the inner conversation will use it." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "gc = GroupChat(\n", + " agents=[mark, alexandra, elizabeth],\n", + " speaker_selection_method=\"auto\",\n", + " allow_repeat_speaker=False,\n", + " select_speaker_auto_verbose=True,\n", + " select_speaker_auto_llm_config=custom_llm_config,\n", + " select_speaker_auto_model_client_cls=MyCustomModelClient,\n", + " max_round=5,\n", + " messages=[],\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With that setup, we create the GroupChatManager, which will use the LLM-based config. So, the custom model client class will only be used for the inner, select speaker, agent of the GroupChat." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\u001b[33mmoderator\u001b[0m (to Mark):\n", + "\n", + "Mark, ask us an accounting question. Alexandra and Elizabeth will help you out.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", + "\u001b[33mMark\u001b[0m (to moderator):\n", + "\n", + "I've been trying to understand how to properly capitalize versus expense certain costs in our company's general ledger. Let me give you some background.\n", + "\n", + "We're a small manufacturing business that just purchased new equipment worth $100,000. The seller is financing 50% of the purchase price as a loan, with an interest rate of 6%. We'll be using this equipment for multiple years to produce goods.\n", + "\n", + "Here's my question: should we capitalize the full $100,000 cost or only the portion that we paid out-of-pocket, which was $50,000?\n", + "\n", + "--------------------------------------------------------------------------------\n", + "[autogen.oai.client: 10-18 00:20:34] {565} INFO - Detected custom model client in config: MyCustomModelClient, model client can not be used until register_model_client is called.\n", + "CustomModelClient config: {'model_client_cls': 'MyCustomModelClient'}\n", + "\u001b[33mchecking_agent\u001b[0m (to speaker_selection_agent):\n", + "\n", + "Read the above conversation. Then select the next role from ['Alexandra', 'Elizabeth'] to play. Only return the role.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mspeaker_selection_agent\u001b[0m (to checking_agent):\n", + "\n", + "Randomly choosing... Alexandra\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[32m>>>>>>>> Select speaker attempt 1 of 3 successfully selected: Alexandra\u001b[0m\n", + "\u001b[32m\n", + "Next speaker: Alexandra\n", + "\u001b[0m\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", + "\u001b[33mAlexandra\u001b[0m (to moderator):\n", + "\n", + "A great question that gets to the heart of accounting principles!\n", + "\n", + "In your case, you'll want to use the Full Amortization Method (also known as Straight-Line Depreciation) to depreciate the equipment over its useful life. This method assumes that the equipment will be used for a certain number of years, and you'll depreciate it evenly over that period.\n", + "\n", + "According to GAAP (Generally Accepted Accounting Principles), you should capitalize the full purchase price of the equipment, which is $100,000. This includes both the cash outlay ($50,000) and the financing portion ($50,000).\n", + "\n", + "Here's why:\n", + "\n", + "1. **GAAP requires capitalization**: When an asset is purchased or constructed, it must be capitalized in its entirety, regardless of how it was financed.\n", + "2. **The equipment has a useful life**: Since you'll be using the equipment for multiple years to produce goods, it will have a useful life beyond the initial year. This means that the entire purchase price should be recognized as an asset on your balance sheet.\n", + "3. **Depreciation will be calculated based on the total cost**: You'll depreciate the equipment over its useful life, using the straight-line method. The depreciation expense will be calculated based on the total cost of the equipment, including both the cash outlay and the financed portion.\n", + "\n", + "To illustrate this:\n", + "\n", + "Assume the equipment has a useful life of 5 years.\n", + "\n", + "* Total cost: $100,000\n", + "* Annual depreciation expense = ($100,000 รท 5) = $20,000 per year\n", + "\n", + "In each of the 5 years, you'll record an annual depreciation expense of $20,000, which will be calculated based on the total cost of the equipment.\n", + "\n", + "In summary, capitalize the full $100,000 purchase price of the equipment, and depreciate it over its useful life using the straight-line method. This ensures that your accounting records accurately reflect the economic reality of owning an asset with a finite useful life.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "[autogen.oai.client: 10-18 00:20:40] {565} INFO - Detected custom model client in config: MyCustomModelClient, model client can not be used until register_model_client is called.\n", + "CustomModelClient config: {'model_client_cls': 'MyCustomModelClient'}\n", + "\u001b[33mchecking_agent\u001b[0m (to speaker_selection_agent):\n", + "\n", + "Read the above conversation. Then select the next role from ['Mark', 'Elizabeth'] to play. Only return the role.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mspeaker_selection_agent\u001b[0m (to checking_agent):\n", + "\n", + "Randomly choosing... Mark\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[32m>>>>>>>> Select speaker attempt 1 of 3 successfully selected: Mark\u001b[0m\n", + "\u001b[32m\n", + "Next speaker: Mark\n", + "\u001b[0m\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", + "\u001b[33mMark\u001b[0m (to moderator):\n", + "\n", + "Wow, I'm glad you explained this in such detail! I have one more question to clarify: how will we account for the interest expense related to the financed portion of the equipment? Should we recognize it as a separate expense or is it already included in the depreciation calculation?\n", + "\n", + "In other words, do we need to make an additional journal entry to record the interest expense, or is it implicit in the depreciation calculation I mentioned earlier?\n", + "\n", + "--------------------------------------------------------------------------------\n", + "[autogen.oai.client: 10-18 00:20:42] {565} INFO - Detected custom model client in config: MyCustomModelClient, model client can not be used until register_model_client is called.\n", + "CustomModelClient config: {'model_client_cls': 'MyCustomModelClient'}\n", + "\u001b[33mchecking_agent\u001b[0m (to speaker_selection_agent):\n", + "\n", + "Read the above conversation. Then select the next role from ['Alexandra', 'Elizabeth'] to play. Only return the role.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mspeaker_selection_agent\u001b[0m (to checking_agent):\n", + "\n", + "Randomly choosing... Elizabeth\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[32m>>>>>>>> Select speaker attempt 1 of 3 successfully selected: Elizabeth\u001b[0m\n", + "\u001b[32m\n", + "Next speaker: Elizabeth\n", + "\u001b[0m\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", + "\u001b[33mElizabeth\u001b[0m (to moderator):\n", + "\n", + "A well-structured question that gets to the heart of accounting principles indeed!\n", + "\n", + "To clarify, you should recognize both the capitalization of the full purchase price and the separate interest expense related to the financed portion.\n", + "\n", + "The reason for this is that the financing cost (interest) is a distinct economic transaction from the acquisition of the asset. As such, it's necessary to account for it separately as an expense.\n", + "\n", + "When you capitalize the equipment using the full Amortization Method, you're essentially recognizing the present value of the future benefits (or depreciation) associated with owning the asset. However, this method doesn't account for the financing cost explicitly.\n", + "\n", + "To record the interest expense related to the financed portion, you'll need to make an additional journal entry. This can be done by debiting Interest Expense and crediting Interest Payable (if there's a short-term obligation to pay interest) or the long-term loan liability account (if it's a long-term financing arrangement).\n", + "\n", + "To illustrate this:\n", + "\n", + "Assume the same equipment with a $100,000 purchase price and a 6% interest rate on the financed portion ($50,000). The interest expense for each year would be calculated as follows:\n", + "\n", + "Interest Expense = Financed Portion x Interest Rate\n", + "= $50,000 x 6%\n", + "= $3,000 per year\n", + "\n", + "You'll record this interest expense separately from the depreciation calculation. For example, in Year 1, you might have a journal entry like this:\n", + "\n", + "Debit: Interest Expense ($3,000)\n", + "Credit: Interest Payable (or Long-Term Loan Liability) ($3,000)\n", + "\n", + "This way, you're accurately reflecting both the capitalization of the asset and the financing costs associated with its acquisition.\n", + "\n", + "In summary, capitalize the full $100,000 purchase price using the straight-line method, and recognize a separate interest expense related to the financed portion. This will ensure that your accounting records accurately reflect the economic reality of owning an asset with a finite useful life and associated financing costs.\n", + "\n", + "BAZINGA!\n", + "\n", + "--------------------------------------------------------------------------------\n", + "[autogen.oai.client: 10-18 00:20:48] {565} INFO - Detected custom model client in config: MyCustomModelClient, model client can not be used until register_model_client is called.\n", + "CustomModelClient config: {'model_client_cls': 'MyCustomModelClient'}\n", + "\u001b[33mchecking_agent\u001b[0m (to speaker_selection_agent):\n", + "\n", + "Read the above conversation. Then select the next role from ['Mark', 'Alexandra'] to play. Only return the role.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mspeaker_selection_agent\u001b[0m (to checking_agent):\n", + "\n", + "Randomly choosing... Elizabeth\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 ['Mark', 'Alexandra'].\n", + " Respond with ONLY the name of the speaker and DO NOT provide a reason.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mspeaker_selection_agent\u001b[0m (to checking_agent):\n", + "\n", + "Randomly choosing... Elizabeth\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[31m>>>>>>>> Select speaker attempt #2 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 ['Mark', 'Alexandra'].\n", + " Respond with ONLY the name of the speaker and DO NOT provide a reason.\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[33mspeaker_selection_agent\u001b[0m (to checking_agent):\n", + "\n", + "Randomly choosing... Alexandra\n", + "\n", + "--------------------------------------------------------------------------------\n", + "\u001b[32m>>>>>>>> Select speaker attempt 3 of 3 successfully selected: Alexandra\u001b[0m\n", + "\u001b[32m\n", + "Next speaker: Alexandra\n", + "\u001b[0m\n", + "\u001b[31m\n", + ">>>>>>>> USING AUTO REPLY...\u001b[0m\n", + "\u001b[33mAlexandra\u001b[0m (to moderator):\n", + "\n", + "You're not only clever but also enthusiastic about accounting!\n", + "\n", + "I'm glad I could help clarify the accounting treatment for capitalizing the equipment and recognizing the interest expense related to the financed portion. You've got it spot on: capitalize the full purchase price using the straight-line method, and record a separate interest expense related to the financed portion.\n", + "\n", + "The key takeaway is that these are two distinct economic transactions that need to be accounted for separately:\n", + "\n", + "1. **Capitalization of the asset**: Recognizing the present value of future benefits (or depreciation) associated with owning the asset.\n", + "2. **Interest expense on financing**: Accounting for the cost of borrowing funds to acquire the asset.\n", + "\n", + "By making separate journal entries for these two items, you'll ensure that your accounting records accurately reflect the economic reality of your business.\n", + "\n", + "Remember, accounting is all about providing a clear and accurate picture of a company's financial position and performance. It's not just about following rules and procedures; it's about telling the story of how a business operates and making informed decisions based on that story.\n", + "\n", + "Keep asking questions, and I'll be here to help you navigate the world of accounting!\n", + "\n", + "--------------------------------------------------------------------------------\n" + ] + } + ], + "source": [ + "gcm = GroupChatManager(\n", + " groupchat=gc,\n", + " name=\"moderator\",\n", + " system_message=\"You are moderating a chat between a customer, an accountant, and a head accountant. The customer asks a question, the accountant answers it, and the head accountant then validates it.\",\n", + " is_termination_msg=lambda msg: \"BAZINGA\" in msg[\"content\"].lower(),\n", + " llm_config=llm_config,\n", + ")\n", + "\n", + "result = gcm.initiate_chat(\n", + " recipient=mark,\n", + " message=\"Mark, ask us an accounting question. Alexandra and Elizabeth will help you out.\",\n", + " summary_method=\"last_msg\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can see that the inner `speaker_selection_agent` was returning random names for the next agent, highlighting how we can control the configuration that that inner agent used for `auto` speaker selection in group chats." + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.10" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +}