From 339f086b76aa642868783d3f3c115316c0a9f99e Mon Sep 17 00:00:00 2001 From: Kye Date: Mon, 7 Aug 2023 16:23:54 -0400 Subject: [PATCH] swarms banner --- DOCS/agents/README.md | 2 +- README.md | 4 +- images/github-banner-swarms.png | Bin 0 -> 14075 bytes swarms/agents/README.MD | 2 +- swarms/agents/base.py | 18 +- swarms/agents/memory.py | 8 +- swarms/agents/memory/base.py | 7 +- swarms/agents/memory/base_memory.py | 167 ++++++++++++ swarms/agents/memory/chat_message_history.py | 21 ++ swarms/agents/memory/document.py | 81 ++++++ swarms/agents/memory/utils.py | 23 ++ swarms/agents/models/openai.py | 218 ---------------- swarms/agents/models/prompts/base.py | 256 +++++++++++++++++++ swarms/utils/.dockerignore | 3 - swarms/utils/serializable.py | 163 ++++++++++++ 15 files changed, 736 insertions(+), 237 deletions(-) create mode 100644 images/github-banner-swarms.png create mode 100644 swarms/agents/memory/base_memory.py create mode 100644 swarms/agents/memory/chat_message_history.py create mode 100644 swarms/agents/memory/document.py create mode 100644 swarms/agents/memory/utils.py create mode 100644 swarms/agents/models/prompts/base.py delete mode 100644 swarms/utils/.dockerignore create mode 100644 swarms/utils/serializable.py diff --git a/DOCS/agents/README.md b/DOCS/agents/README.md index ddd3a29f..287c69d9 100644 --- a/DOCS/agents/README.md +++ b/DOCS/agents/README.md @@ -1,7 +1,7 @@ Introduction to Agents in Swarms ================================ -Welcome to the revolutionary world of Agents in Swarms. If you're familiar with my philosophy from the Linux world, you'll know that I'm a big believer in simplicity, modularity, and the power of open collaboration. The same principles apply here. +Welcome to the revolutionary world of Agents in Swarms. I'm a big believer in simplicity, modularity, and the power of open collaboration. The same principles apply here. Agents are the individual building blocks in a swarm. They are the worker bees, each with a specific task, but all working together towards a common goal. In our case, an agent is a combination of a Language Model (LLM), Long Term Memory, and Tools. diff --git a/README.md b/README.md index 8dca8469..ea8fc0e8 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@
-# Swarms of Autonomous AI Agents 🤖 🤖 🤖 + - +![Swarming banner icon](images/github-banner-swarms.png) Introducing Swarms, automating all digital activities with multi-agent collaboration, get started in 30 seconds in a seamless onboarding experience. diff --git a/images/github-banner-swarms.png b/images/github-banner-swarms.png new file mode 100644 index 0000000000000000000000000000000000000000..9084ccbf5d3b7321884fbfa3eb30553e124921d2 GIT binary patch literal 14075 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 literal 0 HcmV?d00001 diff --git a/swarms/agents/README.MD b/swarms/agents/README.MD index 53da78dc..4458a064 100644 --- a/swarms/agents/README.MD +++ b/swarms/agents/README.MD @@ -1,7 +1,7 @@ Introduction to Agents in Swarms ================================ -Welcome to the revolutionary world of Agents in Swarms. If you're familiar with my philosophy from the Linux world, you'll know that I'm a big believer in simplicity, modularity, and the power of open collaboration. The same principles apply here. +Welcome to the revolutionary world of Agents in Swarms. I'm a big believer in simplicity, modularity, and the power of open collaboration. The same principles apply here. Agents are the individual building blocks in a swarm. They are the worker bees, each with a specific task, but all working together towards a common goal. In our case, an agent is a combination of a Language Model (LLM), Long Term Memory, and Tools. diff --git a/swarms/agents/base.py b/swarms/agents/base.py index c807afa2..30d1d2dc 100644 --- a/swarms/agents/base.py +++ b/swarms/agents/base.py @@ -3,15 +3,23 @@ from __future__ import annotations from typing import List, Optional from langchain.chains.llm import LLMChain -from langchain.memory import ChatMessageHistory -from langchain.schema import BaseChatMessageHistory, Document -from langchain.vectorstores.base import VectorStoreRetriever from pydantic import ValidationError +from swarms.agents.memory.base import VectorStoreRetriever +from swarms.agents.memory.base_memory import BaseChatMessageHistory +from swarms.agents.memory.chat_message_history import ChatMessageHistory +from swarms.agents.memory.document import Document from swarms.agents.models.base import AbstractModel from swarms.agents.models.prompts.agent_output_parser import AgentOutputParser -from swarms.agents.models.prompts.agent_prompt import AIMessage, HumanMessage, SystemMessage -from swarms.agents.models.prompts.agent_prompt_auto import MessageFormatter, PromptConstructor +from swarms.agents.models.prompts.agent_prompt import ( + AIMessage, + HumanMessage, + SystemMessage, +) +from swarms.agents.models.prompts.agent_prompt_auto import ( + MessageFormatter, + PromptConstructor, +) from swarms.agents.models.prompts.prompt_generator import FINISH_NAME from swarms.agents.tools.base import BaseTool from swarms.agents.utils.Agent import AgentOutputParser diff --git a/swarms/agents/memory.py b/swarms/agents/memory.py index 3a524fee..493e151c 100644 --- a/swarms/agents/memory.py +++ b/swarms/agents/memory.py @@ -1,12 +1,10 @@ from typing import Any, Dict, List from pydantic import Field -from langchain.memory.chat_memory import BaseChatMemory, get_prompt_input_key -from langchain.vectorstores.base import VectorStoreRetriever +from swarms.agents.memory.base_memory import BaseChatMemory, get_prompt_input_key +from swarms.agents.memory.base import VectorStoreRetriever - - -class AutoGPTMemory(BaseChatMemory): +class AgentMemory(BaseChatMemory): retriever: VectorStoreRetriever = Field(exclude=True) """VectorStoreRetriever object to connect to.""" diff --git a/swarms/agents/memory/base.py b/swarms/agents/memory/base.py index d892eee2..6af5015b 100644 --- a/swarms/agents/memory/base.py +++ b/swarms/agents/memory/base.py @@ -25,9 +25,12 @@ from langchain.callbacks.manager import ( AsyncCallbackManagerForRetrieverRun, CallbackManagerForRetrieverRun, ) -from langchain.docstore.document import Document -from langchain.embeddings.base import Embeddings + +from swarms.agents.memory.document import Document +from swarms.utils.embeddings.base import Embeddings + from langchain.schema import BaseRetriever + from pydantic import Field, root_validator VST = TypeVar("VST", bound="VectorStore") diff --git a/swarms/agents/memory/base_memory.py b/swarms/agents/memory/base_memory.py new file mode 100644 index 00000000..6853db38 --- /dev/null +++ b/swarms/agents/memory/base_memory.py @@ -0,0 +1,167 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Any, Dict, List + +from abc import ABC +from typing import Any, Dict, Optional, Tuple + +from pydantic import Field + + +from swarms.agents.models.prompts.base import AIMessage, BaseMessage, HumanMessage +from swarms.utils.serializable import Serializable +from swarms.agents.memory.chat_message_history import ChatMessageHistory + +from langchain.memory.utils import get_prompt_input_key + + +class BaseMemory(Serializable, ABC): + """Abstract base class for memory in Chains. + + Memory refers to state in Chains. Memory can be used to store information about + past executions of a Chain and inject that information into the inputs of + future executions of the Chain. For example, for conversational Chains Memory + can be used to store conversations and automatically add them to future model + prompts so that the model has the necessary context to respond coherently to + the latest input. + + Example: + .. code-block:: python + + class SimpleMemory(BaseMemory): + memories: Dict[str, Any] = dict() + + @property + def memory_variables(self) -> List[str]: + return list(self.memories.keys()) + + def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, str]: + return self.memories + + def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, str]) -> None: + pass + + def clear(self) -> None: + pass + """ # noqa: E501 + + class Config: + """Configuration for this pydantic object.""" + + arbitrary_types_allowed = True + + @property + @abstractmethod + def memory_variables(self) -> List[str]: + """The string keys this memory class will add to chain inputs.""" + + @abstractmethod + def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, Any]: + """Return key-value pairs given the text input to the chain.""" + + @abstractmethod + def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, str]) -> None: + """Save the context of this chain run to memory.""" + + @abstractmethod + def clear(self) -> None: + """Clear memory contents.""" + + + + +class BaseChatMessageHistory(ABC): + """Abstract base class for storing chat message history. + + See `ChatMessageHistory` for default implementation. + + Example: + .. code-block:: python + + class FileChatMessageHistory(BaseChatMessageHistory): + storage_path: str + session_id: str + + @property + def messages(self): + with open(os.path.join(storage_path, session_id), 'r:utf-8') as f: + messages = json.loads(f.read()) + return messages_from_dict(messages) + + def add_message(self, message: BaseMessage) -> None: + messages = self.messages.append(_message_to_dict(message)) + with open(os.path.join(storage_path, session_id), 'w') as f: + json.dump(f, messages) + + def clear(self): + with open(os.path.join(storage_path, session_id), 'w') as f: + f.write("[]") + """ + + messages: List[BaseMessage] + """A list of Messages stored in-memory.""" + + def add_user_message(self, message: str) -> None: + """Convenience method for adding a human message string to the store. + + Args: + message: The string contents of a human message. + """ + self.add_message(HumanMessage(content=message)) + + def add_ai_message(self, message: str) -> None: + """Convenience method for adding an AI message string to the store. + + Args: + message: The string contents of an AI message. + """ + self.add_message(AIMessage(content=message)) + + # TODO: Make this an abstractmethod. + def add_message(self, message: BaseMessage) -> None: + """Add a Message object to the store. + + Args: + message: A BaseMessage object to store. + """ + raise NotImplementedError + + @abstractmethod + def clear(self) -> None: + """Remove all messages from the store""" + + + +class BaseChatMemory(BaseMemory, ABC): + """Abstract base class for chat memory.""" + + chat_memory: BaseChatMessageHistory = Field(default_factory=ChatMessageHistory) + output_key: Optional[str] = None + input_key: Optional[str] = None + return_messages: bool = False + + def _get_input_output( + self, inputs: Dict[str, Any], outputs: Dict[str, str] + ) -> Tuple[str, str]: + if self.input_key is None: + prompt_input_key = get_prompt_input_key(inputs, self.memory_variables) + else: + prompt_input_key = self.input_key + if self.output_key is None: + if len(outputs) != 1: + raise ValueError(f"One output key expected, got {outputs.keys()}") + output_key = list(outputs.keys())[0] + else: + output_key = self.output_key + return inputs[prompt_input_key], outputs[output_key] + + def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, str]) -> None: + """Save context from this conversation to buffer.""" + input_str, output_str = self._get_input_output(inputs, outputs) + self.chat_memory.add_user_message(input_str) + self.chat_memory.add_ai_message(output_str) + + def clear(self) -> None: + """Clear memory contents.""" + self.chat_memory.clear() \ No newline at end of file diff --git a/swarms/agents/memory/chat_message_history.py b/swarms/agents/memory/chat_message_history.py new file mode 100644 index 00000000..2d70451b --- /dev/null +++ b/swarms/agents/memory/chat_message_history.py @@ -0,0 +1,21 @@ +from typing import List + +from pydantic import BaseModel + +from swarms.agents.memory.base_memory import BaseChatMessageHistory, BaseMessage + + +class ChatMessageHistory(BaseChatMessageHistory, BaseModel): + """In memory implementation of chat message history. + + Stores messages in an in memory list. + """ + + messages: List[BaseMessage] = [] + + def add_message(self, message: BaseMessage) -> None: + """Add a self-created message to the store""" + self.messages.append(message) + + def clear(self) -> None: + self.messages = [] \ No newline at end of file diff --git a/swarms/agents/memory/document.py b/swarms/agents/memory/document.py new file mode 100644 index 00000000..df2e7ec7 --- /dev/null +++ b/swarms/agents/memory/document.py @@ -0,0 +1,81 @@ +from __future__ import annotations + +from abc import ABC, abstractmethod +from typing import Any, Sequence + +from pydantic import Field + +from swarms.utils.serializable import Serializable + +class Document(Serializable): + """Class for storing a piece of text and associated metadata.""" + + page_content: str + """String text.""" + metadata: dict = Field(default_factory=dict) + """Arbitrary metadata about the page content (e.g., source, relationships to other + documents, etc.). + """ + + +class BaseDocumentTransformer(ABC): + """Abstract base class for document transformation systems. + + A document transformation system takes a sequence of Documents and returns a + sequence of transformed Documents. + + Example: + .. code-block:: python + + class EmbeddingsRedundantFilter(BaseDocumentTransformer, BaseModel): + embeddings: Embeddings + similarity_fn: Callable = cosine_similarity + similarity_threshold: float = 0.95 + + class Config: + arbitrary_types_allowed = True + + def transform_documents( + self, documents: Sequence[Document], **kwargs: Any + ) -> Sequence[Document]: + stateful_documents = get_stateful_documents(documents) + embedded_documents = _get_embeddings_from_stateful_docs( + self.embeddings, stateful_documents + ) + included_idxs = _filter_similar_embeddings( + embedded_documents, self.similarity_fn, self.similarity_threshold + ) + return [stateful_documents[i] for i in sorted(included_idxs)] + + async def atransform_documents( + self, documents: Sequence[Document], **kwargs: Any + ) -> Sequence[Document]: + raise NotImplementedError + + """ # noqa: E501 + + @abstractmethod + def transform_documents( + self, documents: Sequence[Document], **kwargs: Any + ) -> Sequence[Document]: + """Transform a list of documents. + + Args: + documents: A sequence of Documents to be transformed. + + Returns: + A list of transformed Documents. + """ + + @abstractmethod + async def atransform_documents( + self, documents: Sequence[Document], **kwargs: Any + ) -> Sequence[Document]: + """Asynchronously transform a list of documents. + + Args: + documents: A sequence of Documents to be transformed. + + Returns: + A list of transformed Documents. + """ \ No newline at end of file diff --git a/swarms/agents/memory/utils.py b/swarms/agents/memory/utils.py new file mode 100644 index 00000000..5c4792e6 --- /dev/null +++ b/swarms/agents/memory/utils.py @@ -0,0 +1,23 @@ +from typing import Any, Dict, List + +from swarms.agents.memory.base import get_buffer_string + + + +def get_prompt_input_key(inputs: Dict[str, Any], memory_variables: List[str]) -> str: + """ + Get the prompt input key. + + Args: + inputs: Dict[str, Any] + memory_variables: List[str] + + Returns: + A prompt input key. + """ + # "stop" is a special key that can be passed as input but is not used to + # format the prompt. + prompt_input_keys = list(set(inputs).difference(memory_variables + ["stop"])) + if len(prompt_input_keys) != 1: + raise ValueError(f"One input key expected got {prompt_input_keys}") + return prompt_input_keys[0] \ No newline at end of file diff --git a/swarms/agents/models/openai.py b/swarms/agents/models/openai.py index 300b161d..cf47f7d0 100644 --- a/swarms/agents/models/openai.py +++ b/swarms/agents/models/openai.py @@ -557,221 +557,3 @@ class ChatOpenAI(BaseChatModel): - - - - - - -#================================= -# from typing import ( -# Any, -# Dict, -# List, -# Mapping, -# Optional, -# ) - -# import openai - - -# class ChatResult: -# """Wrapper for the result of the chat generation process.""" - -# def __init__( -# self, -# generations: List[ChatGeneration], -# llm_output: Optional[Mapping[str, Any]] = None, -# ): -# self.generations = generations -# self.llm_output = llm_output or {} - -# class BaseMessage: -# """Base class for different types of messages.""" - -# def __init__(self, content: str): -# self.content = content - -# class AIMessage(BaseMessage): -# """Message from the AI Assistant.""" - -# def __init__(self, content: str, additional_kwargs: Optional[Dict[str, Any]] = None): -# super().__init__(content) -# self.additional_kwargs = additional_kwargs or {} - -# class HumanMessage(BaseMessage): -# """Message from the User.""" - -# pass - -# class SystemMessage(BaseMessage): -# """System message.""" - -# pass - -# class FunctionMessage(BaseMessage): -# """Function message.""" - -# def __init__(self, content: str, name: str): -# super().__init__(content) -# self.name = name - -# class ChatGeneration: -# """Wrapper for the chat generation information.""" - -# def __init__( -# self, message: BaseMessage, generation_info: Optional[Mapping[str, Any]] = None -# ): -# self.message = message -# self.generation_info = generation_info or {} - -# class ChatGenerationChunk: -# """Wrapper for a chunk of chat generation.""" - -# def __init__(self, message: BaseMessage): -# self.message = message - -# def get_from_env_or_raise(var_name: str) -> str: -# value = os.getenv(var_name) -# if value is None: -# raise ValueError(f"Environment variable {var_name} is not set.") -# return value - - -# class OpenAI: -# """Wrapper around OpenAI Chat large language models. - -# To use, you should have the ``openai`` python package installed, and the -# environment variable ``OPENAI_API_KEY`` set with your API key. - -# Example: -# .. code-block:: python - -# from langchain.chat_models import OpenAI -# openai = OpenAI(model_name="gpt-3.5-turbo") -# """ - -# def __init__( -# self, -# model_name: str = "gpt-3.5-turbo", -# temperature: float = 0.7, -# openai_api_key: Optional[str] = None, -# request_timeout: Optional[float] = None, -# max_retries: int = 6, -# ): -# self.model_name = model_name -# self.temperature = temperature -# self.openai_api_key = openai_api_key -# self.request_timeout = request_timeout -# self.max_retries = max_retries -# self._initialize_openai() - -# def _initialize_openai(self): -# """Initialize the OpenAI client.""" -# if self.openai_api_key is None: -# raise ValueError("OPENAI_API_KEY environment variable is not set.") - -# openai.api_key = self.openai_api_key - -# def _create_retry_decorator(self): -# """Create a decorator to handle API call retries.""" -# errors = [ -# openai.error.Timeout, -# openai.error.APIError, -# openai.error.APIConnectionError, -# openai.error.RateLimitError, -# openai.error.ServiceUnavailableError, -# ] - -# def retry_decorator(func): -# @wraps(func) -# def wrapper(*args, **kwargs): -# for _ in range(self.max_retries): -# try: -# return func(*args, **kwargs) -# except tuple(errors): -# continue -# raise ValueError("Max retries reached. Unable to complete the API call.") - -# return wrapper - -# return retry_decorator - -# def _create_message_dict(self, message: BaseMessage) -> Dict[str, Any]: -# """Convert a LangChain message to an OpenAI message dictionary.""" -# role = message.role -# content = message.content -# message_dict = {"role": role, "content": content} - -# if role == "assistant" and isinstance(message, AIMessage): -# message_dict["function_call"] = message.additional_kwargs.get("function_call", {}) - -# if role == "function" and isinstance(message, FunctionMessage): -# message_dict["name"] = message.name - -# return message_dict - -# def _create_message_dicts(self, messages: List[BaseMessage]) -> List[Dict[str, Any]]: -# """Convert a list of LangChain messages to a list of OpenAI message dictionaries.""" -# return [self._create_message_dict(message) for message in messages] - -# @retry_decorator -# def _openai_completion(self, messages: List[Dict[str, Any]], params: Dict[str, Any]) -> Any: -# """Call the OpenAI Chat Completion API.""" -# response = openai.ChatCompletion.create(messages=messages, **params) -# return response - -# def generate( -# self, -# messages: List[BaseMessage], -# stop: Optional[List[str]] = None, -# **kwargs: Any, -# ) -> ChatResult: -# """Generate a response using the OpenAI Chat model. - -# Args: -# messages (List[BaseMessage]): List of messages in the conversation. -# stop (Optional[List[str]]): List of stop sequences to stop generation. - -# Returns: -# ChatResult: The generated response wrapped in ChatResult object. -# """ -# params = { -# "model": self.model_name, -# "temperature": self.temperature, -# "max_tokens": kwargs.get("max_tokens"), -# "stream": kwargs.get("streaming", False), -# "n": kwargs.get("n", 1), -# "request_timeout": kwargs.get("request_timeout", self.request_timeout), -# } - -# messages_dicts = self._create_message_dicts(messages) -# response = self._openai_completion(messages_dicts, params) - -# # Process the response and create ChatResult -# generations = [] -# for choice in response["choices"]: -# message = self._convert_message(choice["message"]) -# generation_info = {"finish_reason": choice.get("finish_reason")} -# generation = ChatGeneration(message=message, generation_info=generation_info) -# generations.append(generation) - -# return ChatResult(generations=generations) - -# def _convert_message(self, message_dict: Dict[str, Any]) -> BaseMessage: -# """Convert an OpenAI message dictionary to a LangChain message.""" -# role = message_dict["role"] -# content = message_dict["content"] - -# if role == "user": -# return HumanMessage(content=content) -# elif role == "assistant": -# additional_kwargs = message_dict.get("function_call", {}) -# return AIMessage(content=content, additional_kwargs=additional_kwargs) -# elif role == "system": -# return SystemMessage(content=content) -# elif role == "function": -# name = message_dict.get("name", "") -# return FunctionMessage(content=content, name=name) -# else: -# raise ValueError(f"Invalid role found in the message: {role}") diff --git a/swarms/agents/models/prompts/base.py b/swarms/agents/models/prompts/base.py new file mode 100644 index 00000000..7882b0d5 --- /dev/null +++ b/swarms/agents/models/prompts/base.py @@ -0,0 +1,256 @@ +from __future__ import annotations + +from abc import abstractmethod +from typing import TYPE_CHECKING, Any, Dict, List, Sequence + +from pydantic import Field + +from swarms.utils.serializable import Serializable + +if TYPE_CHECKING: + from langchain.prompts.chat import ChatPromptTemplate + +def get_buffer_string( + messages: Sequence[BaseMessage], human_prefix: str = "Human", ai_prefix: str = "AI" +) -> str: + """Convert sequence of Messages to strings and concatenate them into one string. + + Args: + messages: Messages to be converted to strings. + human_prefix: The prefix to prepend to contents of HumanMessages. + ai_prefix: THe prefix to prepend to contents of AIMessages. + + Returns: + A single string concatenation of all input messages. + + Example: + .. code-block:: python + + from langchain.schema import AIMessage, HumanMessage + + messages = [ + HumanMessage(content="Hi, how are you?"), + AIMessage(content="Good, how are you?"), + ] + get_buffer_string(messages) + # -> "Human: Hi, how are you?\nAI: Good, how are you?" + """ + string_messages = [] + for m in messages: + if isinstance(m, HumanMessage): + role = human_prefix + elif isinstance(m, AIMessage): + role = ai_prefix + elif isinstance(m, SystemMessage): + role = "System" + elif isinstance(m, FunctionMessage): + role = "Function" + elif isinstance(m, ChatMessage): + role = m.role + else: + raise ValueError(f"Got unsupported message type: {m}") + message = f"{role}: {m.content}" + if isinstance(m, AIMessage) and "function_call" in m.additional_kwargs: + message += f"{m.additional_kwargs['function_call']}" + string_messages.append(message) + + return "\n".join(string_messages) + + +class BaseMessage(Serializable): + """The base abstract Message class. + + Messages are the inputs and outputs of ChatModels. + """ + + content: str + """The string contents of the message.""" + + additional_kwargs: dict = Field(default_factory=dict) + """Any additional information.""" + + @property + @abstractmethod + def type(self) -> str: + """Type of the Message, used for serialization.""" + + @property + def lc_serializable(self) -> bool: + """Whether this class is LangChain serializable.""" + return True + + def __add__(self, other: Any) -> ChatPromptTemplate: + from langchain.prompts.chat import ChatPromptTemplate + + prompt = ChatPromptTemplate(messages=[self]) + return prompt + other + + +class BaseMessageChunk(BaseMessage): + def _merge_kwargs_dict( + self, left: Dict[str, Any], right: Dict[str, Any] + ) -> Dict[str, Any]: + """Merge additional_kwargs from another BaseMessageChunk into this one.""" + merged = left.copy() + for k, v in right.items(): + if k not in merged: + merged[k] = v + elif type(merged[k]) != type(v): + raise ValueError( + f'additional_kwargs["{k}"] already exists in this message,' + " but with a different type." + ) + elif isinstance(merged[k], str): + merged[k] += v + elif isinstance(merged[k], dict): + merged[k] = self._merge_kwargs_dict(merged[k], v) + else: + raise ValueError( + f"Additional kwargs key {k} already exists in this message." + ) + return merged + + def __add__(self, other: Any) -> BaseMessageChunk: # type: ignore + if isinstance(other, BaseMessageChunk): + # If both are (subclasses of) BaseMessageChunk, + # concat into a single BaseMessageChunk + + return self.__class__( + content=self.content + other.content, + additional_kwargs=self._merge_kwargs_dict( + self.additional_kwargs, other.additional_kwargs + ), + ) + else: + raise TypeError( + 'unsupported operand type(s) for +: "' + f"{self.__class__.__name__}" + f'" and "{other.__class__.__name__}"' + ) + + +class HumanMessage(BaseMessage): + """A Message from a human.""" + + example: bool = False + """Whether this Message is being passed in to the model as part of an example + conversation. + """ + + @property + def type(self) -> str: + """Type of the message, used for serialization.""" + return "human" + + +class HumanMessageChunk(HumanMessage, BaseMessageChunk): + pass + + +class AIMessage(BaseMessage): + """A Message from an AI.""" + + example: bool = False + """Whether this Message is being passed in to the model as part of an example + conversation. + """ + + @property + def type(self) -> str: + """Type of the message, used for serialization.""" + return "ai" + + +class AIMessageChunk(AIMessage, BaseMessageChunk): + pass + + +class SystemMessage(BaseMessage): + """A Message for priming AI behavior, usually passed in as the first of a sequence + of input messages. + """ + + @property + def type(self) -> str: + """Type of the message, used for serialization.""" + return "system" + + +class SystemMessageChunk(SystemMessage, BaseMessageChunk): + pass + + +class FunctionMessage(BaseMessage): + """A Message for passing the result of executing a function back to a model.""" + + name: str + """The name of the function that was executed.""" + + @property + def type(self) -> str: + """Type of the message, used for serialization.""" + return "function" + + +class FunctionMessageChunk(FunctionMessage, BaseMessageChunk): + pass + + +class ChatMessage(BaseMessage): + """A Message that can be assigned an arbitrary speaker (i.e. role).""" + + role: str + """The speaker / role of the Message.""" + + @property + def type(self) -> str: + """Type of the message, used for serialization.""" + return "chat" + + +class ChatMessageChunk(ChatMessage, BaseMessageChunk): + pass + + +def _message_to_dict(message: BaseMessage) -> dict: + return {"type": message.type, "data": message.dict()} + + +def messages_to_dict(messages: Sequence[BaseMessage]) -> List[dict]: + """Convert a sequence of Messages to a list of dictionaries. + + Args: + messages: Sequence of messages (as BaseMessages) to convert. + + Returns: + List of messages as dicts. + """ + return [_message_to_dict(m) for m in messages] + + +def _message_from_dict(message: dict) -> BaseMessage: + _type = message["type"] + if _type == "human": + return HumanMessage(**message["data"]) + elif _type == "ai": + return AIMessage(**message["data"]) + elif _type == "system": + return SystemMessage(**message["data"]) + elif _type == "chat": + return ChatMessage(**message["data"]) + elif _type == "function": + return FunctionMessage(**message["data"]) + else: + raise ValueError(f"Got unexpected message type: {_type}") + + +def messages_from_dict(messages: List[dict]) -> List[BaseMessage]: + """Convert a sequence of messages from dicts to Message objects. + + Args: + messages: Sequence of messages (as dicts) to convert. + + Returns: + List of messages (BaseMessages). + """ + return [_message_from_dict(m) for m in messages] \ No newline at end of file diff --git a/swarms/utils/.dockerignore b/swarms/utils/.dockerignore deleted file mode 100644 index 8637cefc..00000000 --- a/swarms/utils/.dockerignore +++ /dev/null @@ -1,3 +0,0 @@ -.env -__pycache__ -.venv \ No newline at end of file diff --git a/swarms/utils/serializable.py b/swarms/utils/serializable.py new file mode 100644 index 00000000..6d5a321f --- /dev/null +++ b/swarms/utils/serializable.py @@ -0,0 +1,163 @@ +from abc import ABC +from typing import Any, Dict, List, Literal, TypedDict, Union, cast + +from pydantic import BaseModel, PrivateAttr + + +class BaseSerialized(TypedDict): + """Base class for serialized objects.""" + + lc: int + id: List[str] + + +class SerializedConstructor(BaseSerialized): + """Serialized constructor.""" + + type: Literal["constructor"] + kwargs: Dict[str, Any] + + +class SerializedSecret(BaseSerialized): + """Serialized secret.""" + + type: Literal["secret"] + + +class SerializedNotImplemented(BaseSerialized): + """Serialized not implemented.""" + + type: Literal["not_implemented"] + + +class Serializable(BaseModel, ABC): + """Serializable base class.""" + + @property + def lc_serializable(self) -> bool: + """ + Return whether or not the class is serializable. + """ + return False + + @property + def lc_namespace(self) -> List[str]: + """ + Return the namespace of the langchain object. + eg. ["langchain", "llms", "openai"] + """ + return self.__class__.__module__.split(".") + + @property + def lc_secrets(self) -> Dict[str, str]: + """ + Return a map of constructor argument names to secret ids. + eg. {"openai_api_key": "OPENAI_API_KEY"} + """ + return dict() + + @property + def lc_attributes(self) -> Dict: + """ + Return a list of attribute names that should be included in the + serialized kwargs. These attributes must be accepted by the + constructor. + """ + return {} + + class Config: + extra = "ignore" + + _lc_kwargs = PrivateAttr(default_factory=dict) + + def __init__(self, **kwargs: Any) -> None: + super().__init__(**kwargs) + self._lc_kwargs = kwargs + + def to_json(self) -> Union[SerializedConstructor, SerializedNotImplemented]: + if not self.lc_serializable: + return self.to_json_not_implemented() + + secrets = dict() + # Get latest values for kwargs if there is an attribute with same name + lc_kwargs = { + k: getattr(self, k, v) + for k, v in self._lc_kwargs.items() + if not (self.__exclude_fields__ or {}).get(k, False) # type: ignore + } + + # Merge the lc_secrets and lc_attributes from every class in the MRO + for cls in [None, *self.__class__.mro()]: + # Once we get to Serializable, we're done + if cls is Serializable: + break + + # Get a reference to self bound to each class in the MRO + this = cast(Serializable, self if cls is None else super(cls, self)) + + secrets.update(this.lc_secrets) + lc_kwargs.update(this.lc_attributes) + + # include all secrets, even if not specified in kwargs + # as these secrets may be passed as an environment variable instead + for key in secrets.keys(): + secret_value = getattr(self, key, None) or lc_kwargs.get(key) + if secret_value is not None: + lc_kwargs.update({key: secret_value}) + + return { + "lc": 1, + "type": "constructor", + "id": [*self.lc_namespace, self.__class__.__name__], + "kwargs": lc_kwargs + if not secrets + else _replace_secrets(lc_kwargs, secrets), + } + + def to_json_not_implemented(self) -> SerializedNotImplemented: + return to_json_not_implemented(self) + + +def _replace_secrets( + root: Dict[Any, Any], secrets_map: Dict[str, str] +) -> Dict[Any, Any]: + result = root.copy() + for path, secret_id in secrets_map.items(): + [*parts, last] = path.split(".") + current = result + for part in parts: + if part not in current: + break + current[part] = current[part].copy() + current = current[part] + if last in current: + current[last] = { + "lc": 1, + "type": "secret", + "id": [secret_id], + } + return result + + +def to_json_not_implemented(obj: object) -> SerializedNotImplemented: + """Serialize a "not implemented" object. + + Args: + obj: object to serialize + + Returns: + SerializedNotImplemented + """ + _id: List[str] = [] + try: + if hasattr(obj, "__name__"): + _id = [*obj.__module__.split("."), obj.__name__] + elif hasattr(obj, "__class__"): + _id = [*obj.__class__.__module__.split("."), obj.__class__.__name__] + except Exception: + pass + return { + "lc": 1, + "type": "not_implemented", + "id": _id, + } \ No newline at end of file