From bdf6885cbca0434a58166d9dc14bd7ab955daab5 Mon Sep 17 00:00:00 2001 From: Kye Gomez Date: Tue, 20 May 2025 17:39:44 -0700 Subject: [PATCH] conversation --- docs/mkdocs.yml | 5 +- docs/swarms/structs/conversation.md | 356 +++++++++++++++-------- swarms/structs/conversation.py | 96 +++++- swarms/utils/history_output_formatter.py | 1 + swarms/utils/xml_utils.py | 2 + 5 files changed, 322 insertions(+), 138 deletions(-) diff --git a/docs/mkdocs.yml b/docs/mkdocs.yml index fa976376..8dbc0c12 100644 --- a/docs/mkdocs.yml +++ b/docs/mkdocs.yml @@ -357,7 +357,10 @@ nav: - Swarms API as MCP: "swarms_cloud/mcp.md" - Swarms API Tools: "swarms_cloud/swarms_api_tools.md" - Individual Agent Completions: "swarms_cloud/agent_api.md" - - Swarms API Python Client: "swarms_cloud/python_client.md" + + + - Clients: + - Swarms API Python Client: "swarms_cloud/python_client.md" - Pricing: - Swarms API Pricing: "swarms_cloud/api_pricing.md" diff --git a/docs/swarms/structs/conversation.md b/docs/swarms/structs/conversation.md index 7b849d62..97a78cb9 100644 --- a/docs/swarms/structs/conversation.md +++ b/docs/swarms/structs/conversation.md @@ -7,24 +7,36 @@ The `Conversation` class is a powerful tool for managing and structuring convers ## Table of Contents 1. **Class Definition** - - Overview - - Attributes - -2. **Methods** - - `__init__(self, time_enabled: bool = False, *args, **kwargs)` - - `add(self, role: str, content: str, *args, **kwargs)` - - `delete(self, index: str)` - - `update(self, index: str, role, content)` - - `query(self, index: str)` - - `search(self, keyword: str)` - - `display_conversation(self, detailed: bool = False)` - - `export_conversation(self, filename: str)` - - `import_conversation(self, filename: str)` - - `count_messages_by_role(self)` - - `return_history_as_string(self)` - - `save_as_json(self, filename: str)` - - `load_from_json(self, filename: str)` - - `search_keyword_in_conversation(self, keyword: str)` + + - Overview + + - Attributes + + - Initialization Parameters + +2. **Core Methods** + + - Message Management + + - History Operations + + - Export/Import + + - Search and Query + + - Cache Management + + - Memory Management + +3. **Advanced Features** + + - Token Counting + + - Memory Providers + + - Caching System + + - Batch Operations --- @@ -36,217 +48,303 @@ The `Conversation` class is designed to manage conversations by keeping track of #### Attributes -- `time_enabled (bool)`: A flag indicating whether to enable timestamp recording for messages. -- `conversation_history (list)`: A list that stores messages in the conversation. +- `id (str)`: Unique identifier for the conversation + +- `name (str)`: Name of the conversation -### 2. Methods +- `system_prompt (Optional[str])`: System prompt for the conversation -#### `__init__(self, time_enabled: bool = False, *args, **kwargs)` +- `time_enabled (bool)`: Flag to enable time tracking for messages -- **Description**: Initializes a new Conversation object. -- **Parameters**: - - `time_enabled (bool)`: If `True`, timestamps will be recorded for each message. Default is `False`. +- `autosave (bool)`: Flag to enable automatic saving -#### `add(self, role: str, content: str, *args, **kwargs)` +- `save_filepath (str)`: File path for saving conversation history -- **Description**: Adds a message to the conversation history. -- **Parameters**: - - `role (str)`: The role of the speaker (e.g., "user," "assistant"). - - `content (str)`: The content of the message. +- `conversation_history (list)`: List storing conversation messages -#### `delete(self, index: str)` +- `tokenizer (Any)`: Tokenizer for counting tokens -- **Description**: Deletes a message from the conversation history. -- **Parameters**: - - `index (str)`: The index of the message to delete. +- `context_length (int)`: Maximum number of tokens allowed -#### `update(self, index: str, role, content)` +- `rules (str)`: Rules for the conversation -- **Description**: Updates a message in the conversation history. -- **Parameters**: - - `index (str)`: The index of the message to update. - - `role (_type_)`: The new role of the speaker. - - `content (_type_)`: The new content of the message. +- `custom_rules_prompt (str)`: Custom prompt for rules -#### `query(self, index: str)` +- `user (str)`: User identifier for messages -- **Description**: Retrieves a message from the conversation history. -- **Parameters**: - - `index (str)`: The index of the message to query. -- **Returns**: The message as a string. +- `auto_save (bool)`: Flag for auto-saving -#### `search(self, keyword: str)` +- `save_as_yaml (bool)`: Flag to save as YAML -- **Description**: Searches for messages containing a specific keyword in the conversation history. -- **Parameters**: - - `keyword (str)`: The keyword to search for. -- **Returns**: A list of messages that contain the keyword. -#### `display_conversation(self, detailed: bool = False)` +- `save_as_json_bool (bool)`: Flag to save as JSON -- **Description**: Displays the conversation history. -- **Parameters**: - - `detailed (bool, optional)`: If `True`, provides detailed information about each message. Default is `False`. +- `token_count (bool)`: Flag to enable token counting -#### `export_conversation(self, filename: str)` +- `cache_enabled (bool)`: Flag to enable prompt caching -- **Description**: Exports the conversation history to a text file. -- **Parameters**: - - `filename (str)`: The name of the file to export to. +- `cache_stats (dict)`: Statistics about cache usage -#### `import_conversation(self, filename: str)` +- `provider (Literal["mem0", "in-memory"])`: Memory provider type -- **Description**: Imports a conversation history from a text file. -- **Parameters**: - - `filename (str)`: The name of the file to import from. +#### Initialization Parameters -#### `count_messages_by_role(self)` +```python +conversation = Conversation( + id="unique_id", # Optional: Unique identifier + name="conversation_name", # Optional: Name of conversation + system_prompt="System message", # Optional: Initial system prompt + time_enabled=True, # Optional: Enable timestamps + autosave=True, # Optional: Enable auto-saving + save_filepath="path/to/save.json", # Optional: Save location + tokenizer=your_tokenizer, # Optional: Token counter + context_length=8192, # Optional: Max tokens + rules="conversation rules", # Optional: Rules + custom_rules_prompt="custom", # Optional: Custom rules + user="User:", # Optional: User identifier + auto_save=True, # Optional: Auto-save + save_as_yaml=True, # Optional: Save as YAML + save_as_json_bool=False, # Optional: Save as JSON + token_count=True, # Optional: Count tokens + cache_enabled=True, # Optional: Enable caching + conversations_dir="path/to/dir", # Optional: Cache directory + provider="in-memory" # Optional: Memory provider +) +``` -- **Description**: Counts the number of messages by role in the conversation. -- **Returns**: A dictionary containing the count of messages for each role. +### 2. Core Methods -#### `return_history_as_string(self)` +#### Message Management -- **Description**: Returns the entire conversation history as a single string. -- **Returns**: The conversation history as a string. +##### `add(role: str, content: Union[str, dict, list], metadata: Optional[dict] = None)` -#### `save_as_json(self, filename: str)` +Adds a message to the conversation history. -- **Description**: Saves the conversation history as a JSON file. -- **Parameters**: - - `filename (str)`: The name of the JSON file to save. +```python +# Add a simple text message +conversation.add("user", "Hello, how are you?") -#### `load_from_json(self, filename: str)` +# Add a structured message +conversation.add("assistant", { + "type": "response", + "content": "I'm doing well!" +}) -- **Description**: Loads a conversation history from a JSON file. -- **Parameters**: - - `filename (str)`: The name of the JSON file to load. +# Add with metadata +conversation.add("user", "Hello", metadata={"timestamp": "2024-03-20"}) +``` -#### `search_keyword_in_conversation(self, keyword: str)` +##### `add_multiple_messages(roles: List[str], contents: List[Union[str, dict, list]])` -- **Description**: Searches for a keyword in the conversation history and returns matching messages. -- **Parameters**: - - `keyword (str)`: The keyword to search for. -- **Returns**: A list of messages containing the keyword. +Adds multiple messages at once. -## Examples +```python +conversation.add_multiple_messages( + roles=["user", "assistant"], + contents=["Hello!", "Hi there!"] +) +``` -Here are some usage examples of the `Conversation` class: +##### `add_tool_output_to_agent(role: str, tool_output: dict)` -### Creating a Conversation +Adds a tool output to the conversation. ```python -from swarms.structs import Conversation +conversation.add_tool_output_to_agent( + "tool", + {"name": "calculator", "result": "42"} +) +``` + +#### History Operations + +##### `get_last_message_as_string() -> str` + +Returns the last message as a string. -conv = Conversation() +```python +last_message = conversation.get_last_message_as_string() +# Returns: "assistant: Hello there!" ``` -### Adding Messages +##### `get_final_message() -> str` + +Returns the final message from the conversation. ```python -conv.add("user", "Hello, world!") -conv.add("assistant", "Hello, user!") +final_message = conversation.get_final_message() +# Returns: "assistant: Goodbye!" ``` -### Displaying the Conversation +##### `get_final_message_content() -> str` + +Returns just the content of the final message. ```python -conv.display_conversation() +final_content = conversation.get_final_message_content() +# Returns: "Goodbye!" ``` -### Searching for Messages +##### `return_all_except_first() -> list` + +Returns all messages except the first one. ```python -result = conv.search("Hello") +messages = conversation.return_all_except_first() ``` -### Exporting and Importing Conversations +##### `return_all_except_first_string() -> str` + +Returns all messages except the first one as a string. ```python -conv.export_conversation("conversation.txt") -conv.import_conversation("conversation.txt") +messages_str = conversation.return_all_except_first_string() ``` -### Counting Messages by Role +#### Export/Import + +##### `to_json() -> str` + +Converts conversation to JSON string. ```python -counts = conv.count_messages_by_role() +json_str = conversation.to_json() ``` -### Loading and Saving as JSON +##### `to_dict() -> list` + +Converts conversation to dictionary. ```python -conv.save_as_json("conversation.json") -conv.load_from_json("conversation.json") +dict_data = conversation.to_dict() ``` -Certainly! Let's continue with more examples and additional information about the `Conversation` class. +##### `to_yaml() -> str` + +Converts conversation to YAML string. + +```python +yaml_str = conversation.to_yaml() +``` -### Querying a Specific Message +##### `return_json() -> str` -You can retrieve a specific message from the conversation by its index: +Returns conversation as formatted JSON string. ```python -message = conv.query(0) # Retrieves the first message +json_str = conversation.return_json() ``` -### Updating a Message +#### Search and Query -You can update a message's content or role within the conversation: +##### `get_visible_messages(agent: "Agent", turn: int) -> List[Dict]` + +Gets visible messages for a specific agent and turn. + +```python +visible_msgs = conversation.get_visible_messages(agent, turn=1) +``` + +#### Cache Management + +##### `get_cache_stats() -> Dict[str, int]` + +Gets statistics about cache usage. ```python -conv.update(0, "user", "Hi there!") # Updates the first message +stats = conversation.get_cache_stats() +# Returns: { +# "hits": 10, +# "misses": 5, +# "cached_tokens": 1000, +# "total_tokens": 2000, +# "hit_rate": 0.67 +# } ``` -### Deleting a Message +#### Memory Management + +##### `clear_memory()` -If you want to remove a message from the conversation, you can use the `delete` method: +Clears the conversation memory. ```python -conv.delete(0) # Deletes the first message +conversation.clear_memory() ``` -### Counting Messages by Role +##### `clear()` -You can count the number of messages by role in the conversation: +Clears the conversation history. ```python -counts = conv.count_messages_by_role() -# Example result: {'user': 2, 'assistant': 2} +conversation.clear() ``` -### Exporting and Importing as Text +### 3. Advanced Features + +#### Token Counting -You can export the conversation to a text file and later import it: +The class supports automatic token counting when enabled: ```python -conv.export_conversation("conversation.txt") # Export -conv.import_conversation("conversation.txt") # Import +conversation = Conversation(token_count=True) +conversation.add("user", "Hello world") +# Token count will be automatically calculated and stored ``` -### Exporting and Importing as JSON +#### Memory Providers -Conversations can also be saved and loaded as JSON files: +The class supports different memory providers: ```python -conv.save_as_json("conversation.json") # Save as JSON -conv.load_from_json("conversation.json") # Load from JSON +# In-memory provider (default) +conversation = Conversation(provider="in-memory") + +# Mem0 provider +conversation = Conversation(provider="mem0") ``` -### Searching for a Keyword +#### Caching System -You can search for messages containing a specific keyword within the conversation: +The caching system can be enabled to improve performance: ```python -results = conv.search_keyword_in_conversation("Hello") +conversation = Conversation(cache_enabled=True) +# Messages will be cached for faster retrieval ``` +#### Batch Operations +The class supports batch operations for efficiency: -These examples demonstrate the versatility of the `Conversation` class in managing and interacting with conversation data. Whether you're building a chatbot, conducting analysis, or simply organizing dialogues, this class offers a robust set of tools to help you accomplish your goals. +```python +# Batch add messages +conversation.batch_add([ + {"role": "user", "content": "Hello"}, + {"role": "assistant", "content": "Hi"} +]) +``` + +### Class Methods + +#### `load_conversation(name: str, conversations_dir: Optional[str] = None) -> "Conversation"` + +Loads a conversation from cache. + +```python +conversation = Conversation.load_conversation("my_conversation") +``` + +#### `list_cached_conversations(conversations_dir: Optional[str] = None) -> List[str]` + +Lists all cached conversations. + +```python +conversations = Conversation.list_cached_conversations() +``` ## Conclusion -The `Conversation` class is a valuable utility for handling conversation data in Python. With its ability to add, update, delete, search, export, and import messages, you have the flexibility to work with conversations in various ways. Feel free to explore its features and adapt them to your specific projects and applications. +The `Conversation` class provides a comprehensive set of tools for managing conversations in Python applications. With support for multiple memory providers, caching, token counting, and various export formats, it's suitable for a wide range of use cases from simple chat applications to complex AI systems. -If you have any further questions or need additional assistance, please don't hesitate to ask! \ No newline at end of file +For more information or specific use cases, please refer to the examples above or consult the source code. diff --git a/swarms/structs/conversation.py b/swarms/structs/conversation.py index 9a514f1d..69c9e638 100644 --- a/swarms/structs/conversation.py +++ b/swarms/structs/conversation.py @@ -4,7 +4,15 @@ import json import os import threading import uuid -from typing import TYPE_CHECKING, Any, Dict, List, Optional, Union +from typing import ( + TYPE_CHECKING, + Any, + Dict, + List, + Optional, + Union, + Literal, +) import yaml @@ -16,12 +24,18 @@ from swarms.utils.litellm_tokenizer import count_tokens if TYPE_CHECKING: from swarms.structs.agent import Agent +from loguru import logger + def generate_conversation_id(): """Generate a unique conversation ID.""" return str(uuid.uuid4()) +# Define available providers +providers = Literal["mem0", "in-memory"] + + class Conversation(BaseStructure): """ A class to manage a conversation history, allowing for the addition, deletion, @@ -68,6 +82,7 @@ class Conversation(BaseStructure): token_count: bool = True, cache_enabled: bool = True, conversations_dir: Optional[str] = None, + provider: providers = "in-memory", *args, **kwargs, ): @@ -91,6 +106,7 @@ class Conversation(BaseStructure): self.save_as_json_bool = save_as_json_bool self.token_count = token_count self.cache_enabled = cache_enabled + self.provider = provider self.cache_stats = { "hits": 0, "misses": 0, @@ -98,9 +114,13 @@ class Conversation(BaseStructure): "total_tokens": 0, } self.cache_lock = threading.Lock() + self.conversations_dir = conversations_dir + + self.setup() + def setup(self): # Set up conversations directory - self.conversations_dir = conversations_dir or os.path.join( + self.conversations_dir = self.conversations_dir or os.path.join( os.path.expanduser("~"), ".swarms", "conversations" ) os.makedirs(self.conversations_dir, exist_ok=True) @@ -127,15 +147,33 @@ class Conversation(BaseStructure): self.add("System", self.system_prompt) if self.rules is not None: - self.add("User", rules) + self.add(self.user or "User", self.rules) - if custom_rules_prompt is not None: - self.add(user or "User", custom_rules_prompt) + if self.custom_rules_prompt is not None: + self.add(self.user or "User", self.custom_rules_prompt) # If tokenizer then truncate - if tokenizer is not None: + if self.tokenizer is not None: self.truncate_memory_with_tokenizer() + def mem0_provider(self): + try: + from mem0 import AsyncMemory + except ImportError: + logger.warning( + "mem0ai is not installed. Please install it to use the Conversation class." + ) + return None + + try: + memory = AsyncMemory() + return memory + except Exception as e: + logger.error( + f"Failed to initialize AsyncMemory: {str(e)}" + ) + return None + def _generate_cache_key( self, content: Union[str, dict, list] ) -> str: @@ -230,7 +268,7 @@ class Conversation(BaseStructure): with open(conversation_file, "w") as f: json.dump(save_data, f, indent=4) - def add( + def add_in_memory( self, role: str, content: Union[str, dict, list], @@ -277,6 +315,38 @@ class Conversation(BaseStructure): # Save to cache after adding message self._save_to_cache() + def add_mem0( + self, + role: str, + content: Union[str, dict, list], + metadata: Optional[dict] = None, + ): + """Add a message to the conversation history using the Mem0 provider.""" + if self.provider == "mem0": + memory = self.mem0_provider() + memory.add( + messages=content, + agent_id=role, + run_id=self.id, + metadata=metadata, + ) + + def add( + self, + role: str, + content: Union[str, dict, list], + metadata: Optional[dict] = None, + ): + """Add a message to the conversation history.""" + if self.provider == "in-memory": + self.add_in_memory(role, content) + elif self.provider == "mem0": + self.add_mem0( + role=role, content=content, metadata=metadata + ) + else: + raise ValueError(f"Invalid provider: {self.provider}") + def add_multiple_messages( self, roles: List[str], contents: List[Union[str, dict, list]] ): @@ -570,7 +640,13 @@ class Conversation(BaseStructure): Returns: str: The last message formatted as 'role: content'. """ - return f"{self.conversation_history[-1]['role']}: {self.conversation_history[-1]['content']}" + if self.provider == "mem0": + memory = self.mem0_provider() + return memory.get_all(run_id=self.id) + elif self.provider == "in-memory": + return f"{self.conversation_history[-1]['role']}: {self.conversation_history[-1]['content']}" + else: + raise ValueError(f"Invalid provider: {self.provider}") def return_messages_as_list(self): """Return the conversation messages as a list of formatted strings. @@ -734,6 +810,10 @@ class Conversation(BaseStructure): ) # Remove .json extension return conversations + def clear_memory(self): + """Clear the memory of the conversation.""" + self.conversation_history = [] + # # Example usage # # conversation = Conversation() diff --git a/swarms/utils/history_output_formatter.py b/swarms/utils/history_output_formatter.py index 2ba42d33..ea9d8d7f 100644 --- a/swarms/utils/history_output_formatter.py +++ b/swarms/utils/history_output_formatter.py @@ -20,6 +20,7 @@ HistoryOutputType = Literal[ "str-all-except-first", ] + def history_output_formatter( conversation: Conversation, type: HistoryOutputType = "list" ) -> Union[List[Dict[str, Any]], Dict[str, Any], str]: diff --git a/swarms/utils/xml_utils.py b/swarms/utils/xml_utils.py index 1e310f51..e3ccd308 100644 --- a/swarms/utils/xml_utils.py +++ b/swarms/utils/xml_utils.py @@ -1,6 +1,7 @@ import xml.etree.ElementTree as ET from typing import Any + def dict_to_xml(tag: str, d: dict) -> ET.Element: """Convert a dictionary to an XML Element.""" elem = ET.Element(tag) @@ -21,6 +22,7 @@ def dict_to_xml(tag: str, d: dict) -> ET.Element: elem.append(child) return elem + def to_xml_string(data: Any, root_tag: str = "root") -> str: """Convert a dict or list to an XML string.""" if isinstance(data, dict):