From df02547decd90b4a6396cab01ce3350f9e11268f Mon Sep 17 00:00:00 2001 From: Siddhesh Agarwal Date: Sun, 3 Nov 2024 08:51:37 +0530 Subject: [PATCH 1/5] Create multi-LLM-discussion.py --- examples/multi-LLM-discussion.py | 274 +++++++++++++++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 examples/multi-LLM-discussion.py diff --git a/examples/multi-LLM-discussion.py b/examples/multi-LLM-discussion.py new file mode 100644 index 0000000..408f88d --- /dev/null +++ b/examples/multi-LLM-discussion.py @@ -0,0 +1,274 @@ +import textwrap +from typing import Literal + +from pydantic.main import BaseModel + +from simplemind import generate_text + +MAX_WIDTH = 80 + + +# A member of a discussion (an LLM) +class DiscussionMember(BaseModel): + """The member of a discussion (an LLM)""" + + provider_name: str + provider_model: str + nickname: str + custom_prompt: str | None = None + + +# A message in a conversation +class DiscussionMessage(BaseModel): + """A message in a conversation""" + + content: str + + +class BotMessage(DiscussionMessage): + """The message sent between LLMs""" + + sender: DiscussionMember + + def __str__(self): + return f"{self.sender.nickname}: {self.content}" + + +class ModeratorMessage(DiscussionMessage): + """The message sent by the moderator""" + + visible_to: list[DiscussionMember] = [] + sendor: Literal["Moderator"] = "Moderator" + + def __str__(self): + return f"{self.sendor}: {self.content}" + + +# A discussion +class Discussion: + """Make LLMs discuss something""" + + def __init__(self, topic: str | None = None, *, verbose: bool = False): + self.topic = topic + self.members: list[DiscussionMember] = [] + self.conversation: list[DiscussionMessage] = [] + self.verbose = verbose + + def add_member( + self, + provider_name: str, + provider_model: str, + nickname: str | None = None, + custom_prompt: str | None = None, + ): + """ + add_member Adds a member to the discussion + Parameters + ---------- + provider_name : str + The name of the LLM provider + provider_model : str + The model name of the LLM + nickname : str | None, optional + The nickname of the member, by default the provider_name + custom_prompt : str | None, optional + The custom prompt for the member (visible only to the member), by default None + """ + member = DiscussionMember( + provider_name=provider_name, + provider_model=provider_model, + nickname=nickname or provider_name, + custom_prompt=custom_prompt, + ) + # make sure the nickname is unique + assert member.nickname not in [ + m.nickname for m in self.members + ], f"Duplicate nickname: {member.nickname}" + self.members.append(member) + if self.verbose: + print(f"Added {member.nickname} to the discussion.") + + def get_members(self) -> list[DiscussionMember]: + """Get the members of the discussion""" + return self.members + + def set_topic(self, topic: str): + """Set the topic of the discussion""" + self.topic = topic + + def get_topic(self) -> str | None: + """Get the topic of the discussion""" + return self.topic + + def _get_history_for_member(self, member: DiscussionMember) -> str: + """ + _get_history_for_member Get the history form the POV of the given member. + Parameters + ---------- + member : DiscussionMember + The member to get the history for + Returns + ------- + str + The history as seen by the member + """ + relevant_messages: list[DiscussionMessage] = [] + for message in self.conversation: + if isinstance(message, BotMessage): + relevant_messages.append(message) + elif isinstance(message, ModeratorMessage) and member in message.visible_to: + relevant_messages.append(message) + return "\n\n".join(map(str, relevant_messages)) + + @property + def initial_moderator_message(self) -> str: + return f"Discuss the following topic and answer during your turn only: {self.topic}" + + def _get_response(self, member: DiscussionMember) -> BotMessage: + """ + _get_response Returns the BotMessage from the given member + Parameters + ---------- + member : DiscussionMember + The member to get the response from + Returns + ------- + BotMessage + The BotMessage + """ + + history = self._get_history_for_member(member) + prompt = f"{history}\n\n{member.nickname}: " + content = generate_text( + prompt=prompt, + llm_provider=member.provider_name, + llm_model=member.provider_model, + ) + message = BotMessage( + content=content, + sender=member, + ) + self.conversation.append(message) + if self.verbose: + print(message.sender.nickname) + print("\n".join(textwrap.wrap(message.content, MAX_WIDTH))) + print() + return message + + def add_moderator_message( + self, content: str, visible_to: list[DiscussionMember] | None = None + ): + """ + add_moderator_message adds a message to the conversation as the moderator + Parameters + ---------- + content : str + The content of the message + visible_to : list[DiscussionMember], optional + The list of members that the message is visible to, defaults to all members + """ + if visible_to is None: + visible_to = self.members + message = ModeratorMessage( + content=content, + visible_to=self.members, + ) + self.conversation.append(message) + + def _initialize_discussion(self): + """Initialize the discussion""" + assert self.topic is not None, "Topic must be set" + assert len(self.members) >= 2, "There must be at least 2 members" + self.add_moderator_message(self.initial_moderator_message) + + for member in self.members: + if member.custom_prompt is not None: + self.add_moderator_message(member.custom_prompt, visible_to=[member]) + + if self.verbose: + print(f"Topic: {self.topic}") + print(f"Members: {', '.join(member.nickname for member in self.members)}") + + def discuss(self, no_of_rounds: int = 1): + """ + discuss returns the responses of the members at the end of the discussion. + Parameters + ---------- + no_of_rounds : int, optional + The number of rounds, by default 1. + Round is the number of turns each LLM gets. + verbose : bool, optional + Whether to print the conversation, by default False + Returns + ------- + list[DiscussionMessage] + The conversation between the LLMs + """ + + self._initialize_discussion() + for i in range(no_of_rounds): + for member in self.members: + try: + self._get_response(member) + except Exception as e: + if self.verbose: + print(f"Error: {e}") + continue + if self.verbose: + print(f"Round {i + 1} completed.") + print("=" * MAX_WIDTH) + return self.conversation + + def discuss_yield(self, no_of_rounds: int = 1): + """ + discuss yields the responses of the members during the discussion. + Parameters + ---------- + no_of_rounds : int, optional + The number of rounds, by default 1. + Round is the number of turns each LLM gets. + verbose : bool, optional + Whether to print the conversation, by default False + Returns + ------- + list[DiscussionMessage] + The conversation between the LLMs + """ + + self._initialize_discussion() + for i in range(no_of_rounds): + for member in self.members: + try: + message = self._get_response(member) + yield message + except Exception as e: + if self.verbose: + print(f"Error: {e}") + continue + if self.verbose: + print(f"Round {i + 1} completed.") + print("=" * MAX_WIDTH) + + +if __name__ == "__main__": + discussion = Discussion(verbose=True) + discussion.set_topic("The future of human-AI collaboration in creative fields") + discussion.add_member( + provider_name="openai", + provider_model="gpt-4o-mini", + nickname="Alice", + custom_prompt="You are an AI expert.", + ) + discussion.add_member( + provider_name="openai", + provider_model="gpt-4o", + nickname="Bob", + custom_prompt="You are an Artist.", + ) + discussion.add_member( + provider_name="ollama", + provider_model="llama3.2", + nickname="Charlie", + custom_prompt="You are an Programmer.", + ) + discussion.discuss(3) From 37334a21c5923ffeefefff44d0dd2faa135cfa5e Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Mon, 4 Nov 2024 11:14:02 -0500 Subject: [PATCH 2/5] Update simplemind/providers/anthropic.py Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- simplemind/providers/anthropic.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/simplemind/providers/anthropic.py b/simplemind/providers/anthropic.py index 9adc769..6abb6b8 100644 --- a/simplemind/providers/anthropic.py +++ b/simplemind/providers/anthropic.py @@ -55,10 +55,10 @@ class Anthropic(BaseProvider): """Send a conversation to the Anthropic API.""" from ..models import Message - system_prompt = filter( - lambda msg: msg.role == "system", conversation.messages - ) - + system_messages = [msg for msg in conversation.messages if msg.role == "system"] + if len(system_messages) > 1: + logger.warning("Multiple system messages found. Using the first one.") + system_prompt = system_messages[0] if system_messages else None messages = [ {"role": msg.role, "content": msg.text} for msg in conversation.messages From 44fd3468fa41c8f4877f877ad08540dbda34880d Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Mon, 4 Nov 2024 11:21:07 -0500 Subject: [PATCH 3/5] Revert "Merge pull request #37 from lucianosrp/fix-sys-role-anthropic" This reverts commit 5770c37edf88aa2c3aacf4aae97874f99a735a02, reversing changes made to a5c7486dfcc44751469638e1dbe269c0fe24ff83. --- simplemind/providers/anthropic.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/simplemind/providers/anthropic.py b/simplemind/providers/anthropic.py index 6abb6b8..ffa776d 100644 --- a/simplemind/providers/anthropic.py +++ b/simplemind/providers/anthropic.py @@ -49,24 +49,15 @@ class Anthropic(BaseProvider): return instructor.from_anthropic(self.client) @logger - def send_conversation( - self, conversation: "Conversation", **kwargs - ) -> "Message": + def send_conversation(self, conversation: "Conversation", **kwargs) -> "Message": """Send a conversation to the Anthropic API.""" from ..models import Message - system_messages = [msg for msg in conversation.messages if msg.role == "system"] - if len(system_messages) > 1: - logger.warning("Multiple system messages found. Using the first one.") - system_prompt = system_messages[0] if system_messages else None messages = [ - {"role": msg.role, "content": msg.text} - for msg in conversation.messages - if msg.role != "system" + {"role": msg.role, "content": msg.text} for msg in conversation.messages ] response = self.client.messages.create( - system=next(system_prompt, None), model=conversation.llm_model or self.DEFAULT_MODEL, messages=messages, **{**self.DEFAULT_KWARGS, **kwargs}, From f09052c18e760e42339ce4d3f9f6f64e9473c92d Mon Sep 17 00:00:00 2001 From: Kenneth Reitz Date: Mon, 4 Nov 2024 11:22:10 -0500 Subject: [PATCH 4/5] rename --- examples/{multi-LLM-discussion.py => multi_llm_discussion.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename examples/{multi-LLM-discussion.py => multi_llm_discussion.py} (100%) diff --git a/examples/multi-LLM-discussion.py b/examples/multi_llm_discussion.py similarity index 100% rename from examples/multi-LLM-discussion.py rename to examples/multi_llm_discussion.py From 51c1646ef47f940030da9d07fb0397fd00fb70bd Mon Sep 17 00:00:00 2001 From: Stan Zubarev Date: Tue, 5 Nov 2024 16:41:08 -0500 Subject: [PATCH 5/5] use inference profile with Claude 3.5 on Bedrock --- simplemind/providers/amazon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/simplemind/providers/amazon.py b/simplemind/providers/amazon.py index e286723..fe82d9a 100644 --- a/simplemind/providers/amazon.py +++ b/simplemind/providers/amazon.py @@ -10,7 +10,7 @@ from ..settings import settings T = TypeVar("T", bound=BaseModel) PROVIDER_NAME = "amazon" -DEFAULT_MODEL = "anthropic.claude-3-5-sonnet-20241022-v2:0" +DEFAULT_MODEL = "us.anthropic.claude-3-5-sonnet-20241022-v2:0" DEFAULT_MAX_TOKENS = 5_000