Skip to content

Commit

Permalink
Merge branch 'main' into selectspeakertransforms
Browse files Browse the repository at this point in the history
  • Loading branch information
thinkall authored Aug 12, 2024
2 parents c479a81 + f49ed29 commit 8e3f6d9
Show file tree
Hide file tree
Showing 39 changed files with 2,745 additions and 340 deletions.
30 changes: 27 additions & 3 deletions autogen/agentchat/contrib/capabilities/transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,16 @@ class MessageHistoryLimiter:
It trims the conversation history by removing older messages, retaining only the most recent messages.
"""

def __init__(self, max_messages: Optional[int] = None):
def __init__(self, max_messages: Optional[int] = None, keep_first_message: bool = False):
"""
Args:
max_messages Optional[int]: Maximum number of messages to keep in the context. Must be greater than 0 if not None.
keep_first_message bool: Whether to keep the original first message in the conversation history.
Defaults to False.
"""
self._validate_max_messages(max_messages)
self._max_messages = max_messages
self._keep_first_message = keep_first_message

def apply_transform(self, messages: List[Dict]) -> List[Dict]:
"""Truncates the conversation history to the specified maximum number of messages.
Expand All @@ -75,10 +78,31 @@ def apply_transform(self, messages: List[Dict]) -> List[Dict]:
List[Dict]: A new list containing the most recent messages up to the specified maximum.
"""

if self._max_messages is None:
if self._max_messages is None or len(messages) <= self._max_messages:
return messages

return messages[-self._max_messages :]
truncated_messages = []
remaining_count = self._max_messages

# Start with the first message if we need to keep it
if self._keep_first_message:
truncated_messages = [messages[0]]
remaining_count -= 1

# Loop through messages in reverse
for i in range(len(messages) - 1, 0, -1):
if remaining_count > 1:
truncated_messages.insert(1 if self._keep_first_message else 0, messages[i])
if remaining_count == 1:
# If there's only 1 slot left and it's a 'tools' message, ignore it.
if messages[i].get("role") != "tool":
truncated_messages.insert(1, messages[i])

remaining_count -= 1
if remaining_count == 0:
break

return truncated_messages

def get_logs(self, pre_transform_messages: List[Dict], post_transform_messages: List[Dict]) -> Tuple[str, bool]:
pre_transform_messages_len = len(pre_transform_messages)
Expand Down
77 changes: 67 additions & 10 deletions autogen/agentchat/conversable_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -377,9 +377,9 @@ def replace_reply_func(self, old_reply_func: Callable, new_reply_func: Callable)
f["reply_func"] = new_reply_func

@staticmethod
def _summary_from_nested_chats(
def _get_chats_to_run(
chat_queue: List[Dict[str, Any]], recipient: Agent, messages: Union[str, Callable], sender: Agent, config: Any
) -> Tuple[bool, str]:
) -> List[Dict[str, Any]]:
"""A simple chat reply function.
This function initiate one or a sequence of chats between the "recipient" and the agents in the
chat_queue.
Expand All @@ -406,22 +406,59 @@ def _summary_from_nested_chats(
if message:
current_c["message"] = message
chat_to_run.append(current_c)
return chat_to_run

@staticmethod
def _summary_from_nested_chats(
chat_queue: List[Dict[str, Any]], recipient: Agent, messages: Union[str, Callable], sender: Agent, config: Any
) -> Tuple[bool, Union[str, None]]:
"""A simple chat reply function.
This function initiate one or a sequence of chats between the "recipient" and the agents in the
chat_queue.
It extracts and returns a summary from the nested chat based on the "summary_method" in each chat in chat_queue.
Returns:
Tuple[bool, str]: A tuple where the first element indicates the completion of the chat, and the second element contains the summary of the last chat if any chats were initiated.
"""
chat_to_run = ConversableAgent._get_chats_to_run(chat_queue, recipient, messages, sender, config)
if not chat_to_run:
return True, None
res = initiate_chats(chat_to_run)
return True, res[-1].summary

@staticmethod
async def _a_summary_from_nested_chats(
chat_queue: List[Dict[str, Any]], recipient: Agent, messages: Union[str, Callable], sender: Agent, config: Any
) -> Tuple[bool, Union[str, None]]:
"""A simple chat reply function.
This function initiate one or a sequence of chats between the "recipient" and the agents in the
chat_queue.
It extracts and returns a summary from the nested chat based on the "summary_method" in each chat in chat_queue.
Returns:
Tuple[bool, str]: A tuple where the first element indicates the completion of the chat, and the second element contains the summary of the last chat if any chats were initiated.
"""
chat_to_run = ConversableAgent._get_chats_to_run(chat_queue, recipient, messages, sender, config)
if not chat_to_run:
return True, None
res = await a_initiate_chats(chat_to_run)
index_of_last_chat = chat_to_run[-1]["chat_id"]
return True, res[index_of_last_chat].summary

def register_nested_chats(
self,
chat_queue: List[Dict[str, Any]],
trigger: Union[Type[Agent], str, Agent, Callable[[Agent], bool], List],
reply_func_from_nested_chats: Union[str, Callable] = "summary_from_nested_chats",
position: int = 2,
use_async: Union[bool, None] = None,
**kwargs,
) -> None:
"""Register a nested chat reply function.
Args:
chat_queue (list): a list of chat objects to be initiated.
chat_queue (list): a list of chat objects to be initiated. If use_async is used, then all messages in chat_queue must have a chat-id associated with them.
trigger (Agent class, str, Agent instance, callable, or list): refer to `register_reply` for details.
reply_func_from_nested_chats (Callable, str): the reply function for the nested chat.
The function takes a chat_queue for nested chat, recipient agent, a list of messages, a sender agent and a config as input and returns a reply message.
Expand All @@ -436,15 +473,33 @@ def reply_func_from_nested_chats(
) -> Tuple[bool, Union[str, Dict, None]]:
```
position (int): Ref to `register_reply` for details. Default to 2. It means we first check the termination and human reply, then check the registered nested chat reply.
use_async: Uses a_initiate_chats internally to start nested chats. If the original chat is initiated with a_initiate_chats, you may set this to true so nested chats do not run in sync.
kwargs: Ref to `register_reply` for details.
"""
if reply_func_from_nested_chats == "summary_from_nested_chats":
reply_func_from_nested_chats = self._summary_from_nested_chats
if not callable(reply_func_from_nested_chats):
raise ValueError("reply_func_from_nested_chats must be a callable")
if use_async:
for chat in chat_queue:
if chat.get("chat_id") is None:
raise ValueError("chat_id is required for async nested chats")

if use_async:
if reply_func_from_nested_chats == "summary_from_nested_chats":
reply_func_from_nested_chats = self._a_summary_from_nested_chats
if not callable(reply_func_from_nested_chats) or not inspect.iscoroutinefunction(
reply_func_from_nested_chats
):
raise ValueError("reply_func_from_nested_chats must be a callable and a coroutine")

def wrapped_reply_func(recipient, messages=None, sender=None, config=None):
return reply_func_from_nested_chats(chat_queue, recipient, messages, sender, config)
async def wrapped_reply_func(recipient, messages=None, sender=None, config=None):
return await reply_func_from_nested_chats(chat_queue, recipient, messages, sender, config)

else:
if reply_func_from_nested_chats == "summary_from_nested_chats":
reply_func_from_nested_chats = self._summary_from_nested_chats
if not callable(reply_func_from_nested_chats):
raise ValueError("reply_func_from_nested_chats must be a callable")

def wrapped_reply_func(recipient, messages=None, sender=None, config=None):
return reply_func_from_nested_chats(chat_queue, recipient, messages, sender, config)

functools.update_wrapper(wrapped_reply_func, reply_func_from_nested_chats)

Expand All @@ -454,7 +509,9 @@ def wrapped_reply_func(recipient, messages=None, sender=None, config=None):
position,
kwargs.get("config"),
kwargs.get("reset_config"),
ignore_async_in_sync_chat=kwargs.get("ignore_async_in_sync_chat"),
ignore_async_in_sync_chat=(
not use_async if use_async is not None else kwargs.get("ignore_async_in_sync_chat")
),
)

@property
Expand Down
48 changes: 48 additions & 0 deletions autogen/agentchat/groupchat.py
Original file line number Diff line number Diff line change
Expand Up @@ -986,6 +986,7 @@ def __init__(
# Store groupchat
self._groupchat = groupchat

self._last_speaker = None
self._silent = silent

# Order of register_reply is important.
Expand Down Expand Up @@ -1027,6 +1028,52 @@ def _prepare_chat(
if (recipient != agent or prepare_recipient) and isinstance(agent, ConversableAgent):
agent._prepare_chat(self, clear_history, False, reply_at_receive)

@property
def last_speaker(self) -> Agent:
"""Return the agent who sent the last message to group chat manager.
In a group chat, an agent will always send a message to the group chat manager, and the group chat manager will
send the message to all other agents in the group chat. So, when an agent receives a message, it will always be
from the group chat manager. With this property, the agent receiving the message can know who actually sent the
message.
Example:
```python
from autogen import ConversableAgent
from autogen import GroupChat, GroupChatManager
def print_messages(recipient, messages, sender, config):
# Print the message immediately
print(
f"Sender: {sender.name} | Recipient: {recipient.name} | Message: {messages[-1].get('content')}"
)
print(f"Real Sender: {sender.last_speaker.name}")
assert sender.last_speaker.name in messages[-1].get("content")
return False, None # Required to ensure the agent communication flow continues
agent_a = ConversableAgent("agent A", default_auto_reply="I'm agent A.")
agent_b = ConversableAgent("agent B", default_auto_reply="I'm agent B.")
agent_c = ConversableAgent("agent C", default_auto_reply="I'm agent C.")
for agent in [agent_a, agent_b, agent_c]:
agent.register_reply(
[ConversableAgent, None], reply_func=print_messages, config=None
)
group_chat = GroupChat(
[agent_a, agent_b, agent_c],
messages=[],
max_round=6,
speaker_selection_method="random",
allow_repeat_speaker=True,
)
chat_manager = GroupChatManager(group_chat)
groupchat_result = agent_a.initiate_chat(
chat_manager, message="Hi, there, I'm agent A."
)
"""
return self._last_speaker

def run_chat(
self,
messages: Optional[List[Dict]] = None,
Expand Down Expand Up @@ -1055,6 +1102,7 @@ def run_chat(
a.previous_cache = a.client_cache
a.client_cache = self.client_cache
for i in range(groupchat.max_round):
self._last_speaker = speaker
groupchat.append(message, speaker)
# broadcast the message to all agents except the speaker
for agent in groupchat.agents:
Expand Down
5 changes: 4 additions & 1 deletion autogen/oai/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -457,10 +457,13 @@ 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"]
for key in required_keys:
if key in config:
openai_config[key] = config[key]
for key in optional_keys:
if key in config:
openai_config[key] = config[key]

def _register_default_client(self, config: Dict[str, Any], openai_config: Dict[str, Any]) -> None:
"""Create a client with the given config to override openai_config,
Expand Down
21 changes: 21 additions & 0 deletions dotnet/AutoGen.sln
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.OpenAI.Sample", "sa
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}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AutoGen.Tests.Share", "test\AutoGen.Test.Share\AutoGen.Tests.Share.csproj", "{143725E2-206C-4D37-93E4-9EDF699826B2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -194,6 +200,18 @@ Global
{12079C18-A519-403F-BBFD-200A36A0C083}.Debug|Any CPU.Build.0 = Debug|Any CPU
{12079C18-A519-403F-BBFD-200A36A0C083}.Release|Any CPU.ActiveCfg = Release|Any CPU
{12079C18-A519-403F-BBFD-200A36A0C083}.Release|Any CPU.Build.0 = Release|Any CPU
{5C45981D-1319-4C25-935C-83D411CB28DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5C45981D-1319-4C25-935C-83D411CB28DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5C45981D-1319-4C25-935C-83D411CB28DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5C45981D-1319-4C25-935C-83D411CB28DF}.Release|Any CPU.Build.0 = Release|Any CPU
{5970868F-831E-418F-89A9-4EC599563E16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{5970868F-831E-418F-89A9-4EC599563E16}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5970868F-831E-418F-89A9-4EC599563E16}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5970868F-831E-418F-89A9-4EC599563E16}.Release|Any CPU.Build.0 = Release|Any CPU
{143725E2-206C-4D37-93E4-9EDF699826B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{143725E2-206C-4D37-93E4-9EDF699826B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{143725E2-206C-4D37-93E4-9EDF699826B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{143725E2-206C-4D37-93E4-9EDF699826B2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -229,6 +247,9 @@ Global
{6B82F26D-5040-4453-B21B-C8D1F913CE4C} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
{0E635268-351C-4A6B-A28D-593D868C2CA4} = {FBFEAD1F-29EB-4D99-A672-0CD8473E10B9}
{12079C18-A519-403F-BBFD-200A36A0C083} = {FBFEAD1F-29EB-4D99-A672-0CD8473E10B9}
{5C45981D-1319-4C25-935C-83D411CB28DF} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
{5970868F-831E-418F-89A9-4EC599563E16} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
{143725E2-206C-4D37-93E4-9EDF699826B2} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {93384647-528D-46C8-922C-8DB36A382F0B}
Expand Down
1 change: 1 addition & 0 deletions dotnet/eng/Version.props
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
<MicrosoftASPNETCoreVersion>8.0.4</MicrosoftASPNETCoreVersion>
<GoogleCloudAPIPlatformVersion>3.0.0</GoogleCloudAPIPlatformVersion>
<JsonSchemaVersion>4.3.0.2</JsonSchemaVersion>
<AzureAIInferenceVersion>1.0.0-beta.1</AzureAIInferenceVersion>
<PowershellSDKVersion>7.4.4</PowershellSDKVersion>
</PropertyGroup>
</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public async Task CodeSnippet1()

#region code_snippet_1_1
var kernel = DotnetInteractiveKernelBuilder
.CreateDefaultBuilder() // add C# and F# kernels
.CreateDefaultInProcessKernelBuilder() // add C# and F# kernels
.Build();
#endregion code_snippet_1_1

Expand Down Expand Up @@ -67,7 +67,7 @@ public async Task CodeSnippet1()

#region code_snippet_1_4
var pythonKernel = DotnetInteractiveKernelBuilder
.CreateDefaultBuilder()
.CreateDefaultInProcessKernelBuilder()
.AddPythonKernel(venv: "python3")
.Build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ public static async Task RunAsync()
var instance = new Example04_Dynamic_GroupChat_Coding_Task();

var kernel = DotnetInteractiveKernelBuilder
.CreateDefaultBuilder()
.CreateDefaultInProcessKernelBuilder()
.AddPythonKernel("python3")
.Build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,9 @@ public static async Task<IAgent> CreateReviewerAgentAsync(OpenAIClient openAICli
public static async Task RunWorkflowAsync()
{
long the39thFibonacciNumber = 63245986;
var kernel = DotnetInteractiveKernelBuilder.CreateDefaultBuilder().Build();
var kernel = DotnetInteractiveKernelBuilder
.CreateDefaultInProcessKernelBuilder()
.Build();

var config = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo();
var openaiClient = new OpenAIClient(new Uri(config.Endpoint), new Azure.AzureKeyCredential(config.ApiKey));
Expand Down Expand Up @@ -344,7 +346,9 @@ public static async Task RunAsync()
var config = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo();
var openaiClient = new OpenAIClient(new Uri(config.Endpoint), new Azure.AzureKeyCredential(config.ApiKey));

var kernel = DotnetInteractiveKernelBuilder.CreateDefaultBuilder().Build();
var kernel = DotnetInteractiveKernelBuilder
.CreateDefaultInProcessKernelBuilder()
.Build();
#region create_group_chat
var reviewer = await CreateReviewerAgentAsync(openaiClient, config.DeploymentName);
var coder = await CreateCoderAgentAsync(openaiClient, config.DeploymentName);
Expand Down
Loading

0 comments on commit 8e3f6d9

Please sign in to comment.