parent
51a271172f
commit
300f26880e
@ -0,0 +1,132 @@
|
||||
# Conversation Module Documentation
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Introduction](#introduction)
|
||||
2. [Installation](#installation)
|
||||
3. [Class: Conversation](#class-conversation)
|
||||
- [Attributes](#attributes)
|
||||
- [Methods](#methods)
|
||||
4. [Usage Examples](#usage-examples)
|
||||
- [Example 1: Creating a Conversation](#example-1-creating-a-conversation)
|
||||
- [Example 2: Adding Messages](#example-2-adding-messages)
|
||||
- [Example 3: Displaying and Exporting Conversation](#example-3-displaying-and-exporting-conversation)
|
||||
- [Example 4: Counting Messages by Role](#example-4-counting-messages-by-role)
|
||||
- [Example 5: Loading and Searching](#example-5-loading-and-searching)
|
||||
5. [Additional Information](#additional-information)
|
||||
6. [References](#references)
|
||||
|
||||
---
|
||||
|
||||
## 1. Introduction <a name="introduction"></a>
|
||||
|
||||
The Conversation module provides a versatile and extensible structure for managing and analyzing text-based conversations. Whether you're developing a chatbot, analyzing customer support interactions, or conducting research on dialogues, this module simplifies the process of handling conversation data.
|
||||
|
||||
With the Conversation module, you can add, delete, update, query, and search for messages within a conversation. You can also display, export, and import conversation history, making it an essential tool for various applications.
|
||||
|
||||
## 2. Installation <a name="installation"></a>
|
||||
|
||||
To use the Conversation module, you need to have Python installed on your system. Additionally, you can install the required dependencies using pip:
|
||||
|
||||
```bash
|
||||
pip install termcolor
|
||||
```
|
||||
|
||||
Once you have the dependencies installed, you can import the Conversation module into your Python code.
|
||||
|
||||
```python
|
||||
from swarms.structs.conversation import Conversation
|
||||
```
|
||||
|
||||
## 3. Class: Conversation <a name="class-conversation"></a>
|
||||
|
||||
The Conversation class is the core of this module. It allows you to create and manipulate conversation histories. Below are the attributes and methods provided by this class.
|
||||
|
||||
### Attributes <a name="attributes"></a>
|
||||
|
||||
- `time_enabled` (bool): Indicates whether timestamps are enabled for messages in the conversation.
|
||||
- `conversation_history` (list): A list that stores the conversation history as a collection of messages.
|
||||
|
||||
### Methods <a name="methods"></a>
|
||||
|
||||
The Conversation class provides the following methods:
|
||||
|
||||
- `add(role: str, content: str, *args, **kwargs)`: Adds a message to the conversation history.
|
||||
- `delete(index: str)`: Deletes a message from the conversation history.
|
||||
- `update(index: str, role, content)`: Updates a message in the conversation history.
|
||||
- `query(index: str)`: Queries a message in the conversation history.
|
||||
- `search(keyword: str)`: Searches for messages containing a specific keyword.
|
||||
- `display_conversation(detailed: bool = False)`: Displays the conversation history.
|
||||
- `export_conversation(filename: str)`: Exports the conversation history to a file.
|
||||
- `import_conversation(filename: str)`: Imports a conversation history from a file.
|
||||
- `count_messages_by_role()`: Counts the number of messages by role.
|
||||
- `return_history_as_string()`: Returns the conversation history as a string.
|
||||
- `save_as_json(filename: str)`: Saves the conversation history as a JSON file.
|
||||
- `load_from_json(filename: str)`: Loads the conversation history from a JSON file.
|
||||
- `search_keyword_in_conversation(keyword: str)`: Searches for a keyword in the conversation history.
|
||||
- `pretty_print_conversation(messages)`: Pretty prints the conversation history.
|
||||
|
||||
## 4. Usage Examples <a name="usage-examples"></a>
|
||||
|
||||
In this section, we'll provide practical examples of how to use the Conversation module to manage and analyze conversation data.
|
||||
|
||||
### Example 1: Creating a Conversation <a name="example-1-creating-a-conversation"></a>
|
||||
|
||||
Let's start by creating a Conversation object and enabling timestamps for messages:
|
||||
|
||||
```python
|
||||
conversation = Conversation(time_enabled=True)
|
||||
```
|
||||
|
||||
### Example 2: Adding Messages <a name="example-2-adding-messages"></a>
|
||||
|
||||
You can add messages to the conversation using the `add` method. Here's how to add a user message and an assistant response:
|
||||
|
||||
```python
|
||||
conversation.add("user", "Hello, how can I help you?")
|
||||
conversation.add("assistant", "Hi there! I'm here to assist you.")
|
||||
```
|
||||
|
||||
### Example 3: Displaying and Exporting Conversation <a name="example-3-displaying-and-exporting-conversation"></a>
|
||||
|
||||
You can display the conversation history and export it to a file. Let's see how to do this:
|
||||
|
||||
```python
|
||||
# Display the conversation
|
||||
conversation.display_conversation()
|
||||
|
||||
# Export the conversation to a file
|
||||
conversation.export_conversation("conversation_history.txt")
|
||||
```
|
||||
|
||||
### Example 4: Counting Messages by Role <a name="example-4-counting-messages-by-role"></a>
|
||||
|
||||
You can count the number of messages by role (e.g., user, assistant, system) using the `count_messages_by_role` method:
|
||||
|
||||
```python
|
||||
message_counts = conversation.count_messages_by_role()
|
||||
print(message_counts)
|
||||
```
|
||||
|
||||
### Example 5: Loading and Searching <a name="example-5-loading-and-searching"></a>
|
||||
|
||||
You can load a conversation from a file and search for messages containing a specific keyword:
|
||||
|
||||
```python
|
||||
# Load conversation from a file
|
||||
conversation.load_from_json("saved_conversation.json")
|
||||
|
||||
# Search for messages containing the keyword "help"
|
||||
results = conversation.search("help")
|
||||
print(results)
|
||||
```
|
||||
|
||||
## 5. Additional Information <a name="additional-information"></a>
|
||||
|
||||
- The Conversation module is designed to provide flexibility and ease of use for managing and analyzing text-based conversations.
|
||||
- You can extend the module by adding custom functionality or integrating it into your chatbot or natural language processing applications.
|
||||
|
||||
## 6. References <a name="references"></a>
|
||||
|
||||
For more information on the Conversation module and its usage, refer to the official documentation and examples.
|
||||
|
@ -1,5 +1,6 @@
|
||||
from swarms.structs.agent import Agent
|
||||
from swarms.structs.sequential_workflow import SequentialWorkflow
|
||||
from swarms.structs.autoscaler import AutoScaler
|
||||
from swarms.structs.conversation import Conversation
|
||||
|
||||
__all__ = ["Agent", "SequentialWorkflow", "AutoScaler"]
|
||||
__all__ = ["Agent", "SequentialWorkflow", "AutoScaler", "Conversation"]
|
||||
|
@ -0,0 +1,242 @@
|
||||
import json
|
||||
import datetime
|
||||
|
||||
from termcolor import colored
|
||||
|
||||
from swarms.structs.base import BaseStructure
|
||||
|
||||
|
||||
class Conversation(BaseStructure):
|
||||
def __init__(self, time_enabled: bool = False, *args, **kwargs):
|
||||
super().__init__()
|
||||
self.time_enabled = time_enabled
|
||||
self.conversation_history = []
|
||||
|
||||
def add(self, role: str, content: str, *args, **kwargs):
|
||||
"""Add a message to the conversation history
|
||||
|
||||
Args:
|
||||
role (str): The role of the speaker
|
||||
content (str): The content of the message
|
||||
|
||||
"""
|
||||
if self.time_enabled:
|
||||
now = datetime.datetime.now()
|
||||
timestamp = now.strftime("%Y-%m-%d %H:%M:%S")
|
||||
message = {
|
||||
"role": role,
|
||||
"content": content,
|
||||
"timestamp": timestamp,
|
||||
}
|
||||
else:
|
||||
message = {
|
||||
"role": role,
|
||||
"content": content,
|
||||
}
|
||||
|
||||
self.conversation_history.append(message)
|
||||
|
||||
def delete(self, index: str):
|
||||
"""Delete a message from the conversation history
|
||||
|
||||
Args:
|
||||
index (str): index of the message to delete
|
||||
"""
|
||||
self.conversation_history.pop(index)
|
||||
|
||||
def update(self, index: str, role, content):
|
||||
"""Update a message in the conversation history
|
||||
|
||||
Args:
|
||||
index (str): index of the message to update
|
||||
role (_type_): role of the speaker
|
||||
content (_type_): content of the message
|
||||
"""
|
||||
self.conversation_history[index] = {
|
||||
"role": role,
|
||||
"content": content,
|
||||
}
|
||||
|
||||
def query(self, index: str):
|
||||
"""Query a message in the conversation history
|
||||
|
||||
Args:
|
||||
index (str): index of the message to query
|
||||
|
||||
Returns:
|
||||
str: the message
|
||||
"""
|
||||
return self.conversation_history[index]
|
||||
|
||||
def search(self, keyword: str):
|
||||
"""Search for a message in the conversation history
|
||||
|
||||
Args:
|
||||
keyword (str): Keyword to search for
|
||||
|
||||
Returns:
|
||||
str: description
|
||||
"""
|
||||
return [
|
||||
msg
|
||||
for msg in self.conversation_history
|
||||
if keyword in msg["content"]
|
||||
]
|
||||
|
||||
def display_conversation(self, detailed: bool = False):
|
||||
"""Display the conversation history
|
||||
|
||||
Args:
|
||||
detailed (bool, optional): detailed. Defaults to False.
|
||||
"""
|
||||
role_to_color = {
|
||||
"system": "red",
|
||||
"user": "green",
|
||||
"assistant": "blue",
|
||||
"function": "magenta",
|
||||
}
|
||||
for message in self.conversation_history:
|
||||
print(
|
||||
colored(
|
||||
f"{message['role']}: {message['content']}\n\n",
|
||||
role_to_color[message["role"]],
|
||||
)
|
||||
)
|
||||
|
||||
def export_conversation(self, filename: str):
|
||||
"""Export the conversation history to a file
|
||||
|
||||
Args:
|
||||
filename (str): filename to export to
|
||||
"""
|
||||
with open(filename, "w") as f:
|
||||
for message in self.conversation_history:
|
||||
f.write(f"{message['role']}: {message['content']}\n")
|
||||
|
||||
def import_conversation(self, filename: str):
|
||||
"""Import a conversation history from a file
|
||||
|
||||
Args:
|
||||
filename (str): filename to import from
|
||||
"""
|
||||
with open(filename, "r") as f:
|
||||
for line in f:
|
||||
role, content = line.split(": ", 1)
|
||||
self.add(role, content.strip())
|
||||
|
||||
def count_messages_by_role(self):
|
||||
"""Count the number of messages by role"""
|
||||
counts = {
|
||||
"system": 0,
|
||||
"user": 0,
|
||||
"assistant": 0,
|
||||
"function": 0,
|
||||
}
|
||||
for message in self.conversation_history:
|
||||
counts[message["role"]] += 1
|
||||
return counts
|
||||
|
||||
def return_history_as_string(self):
|
||||
"""Return the conversation history as a string
|
||||
|
||||
Returns:
|
||||
str: the conversation history
|
||||
"""
|
||||
return "\n".join(
|
||||
[
|
||||
f"{message['role']}: {message['content']}\n\n"
|
||||
for message in self.conversation_history
|
||||
]
|
||||
)
|
||||
|
||||
def save_as_json(self, filename: str):
|
||||
"""Save the conversation history as a JSON file
|
||||
|
||||
Args:
|
||||
filename (str): Save the conversation history as a JSON file
|
||||
"""
|
||||
# Save the conversation history as a JSON file
|
||||
with open(filename, "w") as f:
|
||||
json.dump(self.conversation_history, f)
|
||||
|
||||
def load_from_json(self, filename: str):
|
||||
"""Load the conversation history from a JSON file
|
||||
|
||||
Args:
|
||||
filename (str): filename to load from
|
||||
"""
|
||||
# Load the conversation history from a JSON file
|
||||
with open(filename, "r") as f:
|
||||
self.conversation_history = json.load(f)
|
||||
|
||||
def search_keyword_in_conversation(self, keyword: str):
|
||||
"""Search for a keyword in the conversation history
|
||||
|
||||
Args:
|
||||
keyword (str): keyword to search for
|
||||
|
||||
Returns:
|
||||
str: description
|
||||
"""
|
||||
return [
|
||||
msg
|
||||
for msg in self.conversation_history
|
||||
if keyword in msg["content"]
|
||||
]
|
||||
|
||||
def pretty_print_conversation(self, messages):
|
||||
"""Pretty print the conversation history
|
||||
|
||||
Args:
|
||||
messages (str): messages to print
|
||||
"""
|
||||
role_to_color = {
|
||||
"system": "red",
|
||||
"user": "green",
|
||||
"assistant": "blue",
|
||||
"tool": "magenta",
|
||||
}
|
||||
|
||||
for message in messages:
|
||||
if message["role"] == "system":
|
||||
print(
|
||||
colored(
|
||||
f"system: {message['content']}\n",
|
||||
role_to_color[message["role"]],
|
||||
)
|
||||
)
|
||||
elif message["role"] == "user":
|
||||
print(
|
||||
colored(
|
||||
f"user: {message['content']}\n",
|
||||
role_to_color[message["role"]],
|
||||
)
|
||||
)
|
||||
elif message["role"] == "assistant" and message.get(
|
||||
"function_call"
|
||||
):
|
||||
print(
|
||||
colored(
|
||||
f"assistant: {message['function_call']}\n",
|
||||
role_to_color[message["role"]],
|
||||
)
|
||||
)
|
||||
elif message["role"] == "assistant" and not message.get(
|
||||
"function_call"
|
||||
):
|
||||
print(
|
||||
colored(
|
||||
f"assistant: {message['content']}\n",
|
||||
role_to_color[message["role"]],
|
||||
)
|
||||
)
|
||||
elif message["role"] == "tool":
|
||||
print(
|
||||
colored(
|
||||
(
|
||||
f"function ({message['name']}):"
|
||||
f" {message['content']}\n"
|
||||
),
|
||||
role_to_color[message["role"]],
|
||||
)
|
||||
)
|
@ -0,0 +1,241 @@
|
||||
import pytest
|
||||
from swarms.structs.conversation import Conversation
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def conversation():
|
||||
conv = Conversation()
|
||||
conv.add("user", "Hello, world!")
|
||||
conv.add("assistant", "Hello, user!")
|
||||
return conv
|
||||
|
||||
|
||||
def test_add_message():
|
||||
conv = Conversation()
|
||||
conv.add("user", "Hello, world!")
|
||||
assert len(conv.conversation_history) == 1
|
||||
assert conv.conversation_history[0]["role"] == "user"
|
||||
assert conv.conversation_history[0]["content"] == "Hello, world!"
|
||||
|
||||
|
||||
def test_add_message_with_time():
|
||||
conv = Conversation(time_enabled=True)
|
||||
conv.add("user", "Hello, world!")
|
||||
assert len(conv.conversation_history) == 1
|
||||
assert conv.conversation_history[0]["role"] == "user"
|
||||
assert conv.conversation_history[0]["content"] == "Hello, world!"
|
||||
assert "timestamp" in conv.conversation_history[0]
|
||||
|
||||
|
||||
def test_delete_message():
|
||||
conv = Conversation()
|
||||
conv.add("user", "Hello, world!")
|
||||
conv.delete(0)
|
||||
assert len(conv.conversation_history) == 0
|
||||
|
||||
|
||||
def test_delete_message_out_of_bounds():
|
||||
conv = Conversation()
|
||||
conv.add("user", "Hello, world!")
|
||||
with pytest.raises(IndexError):
|
||||
conv.delete(1)
|
||||
|
||||
|
||||
def test_update_message():
|
||||
conv = Conversation()
|
||||
conv.add("user", "Hello, world!")
|
||||
conv.update(0, "assistant", "Hello, user!")
|
||||
assert len(conv.conversation_history) == 1
|
||||
assert conv.conversation_history[0]["role"] == "assistant"
|
||||
assert conv.conversation_history[0]["content"] == "Hello, user!"
|
||||
|
||||
|
||||
def test_update_message_out_of_bounds():
|
||||
conv = Conversation()
|
||||
conv.add("user", "Hello, world!")
|
||||
with pytest.raises(IndexError):
|
||||
conv.update(1, "assistant", "Hello, user!")
|
||||
|
||||
|
||||
def test_return_history_as_string_with_messages(conversation):
|
||||
result = conversation.return_history_as_string()
|
||||
assert result is not None
|
||||
|
||||
|
||||
def test_return_history_as_string_with_no_messages():
|
||||
conv = Conversation()
|
||||
result = conv.return_history_as_string()
|
||||
assert result == ""
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"role, content",
|
||||
[
|
||||
("user", "Hello, world!"),
|
||||
("assistant", "Hello, user!"),
|
||||
("system", "System message"),
|
||||
("function", "Function message"),
|
||||
],
|
||||
)
|
||||
def test_return_history_as_string_with_different_roles(role, content):
|
||||
conv = Conversation()
|
||||
conv.add(role, content)
|
||||
result = conv.return_history_as_string()
|
||||
expected = f"{role}: {content}\n\n"
|
||||
assert result == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("message_count", range(1, 11))
|
||||
def test_return_history_as_string_with_multiple_messages(
|
||||
message_count,
|
||||
):
|
||||
conv = Conversation()
|
||||
for i in range(message_count):
|
||||
conv.add("user", f"Message {i + 1}")
|
||||
result = conv.return_history_as_string()
|
||||
expected = "".join(
|
||||
[f"user: Message {i + 1}\n\n" for i in range(message_count)]
|
||||
)
|
||||
assert result == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"content",
|
||||
[
|
||||
"Hello, world!",
|
||||
"This is a longer message with multiple words.",
|
||||
"This message\nhas multiple\nlines.",
|
||||
"This message has special characters: !@#$%^&*()",
|
||||
"This message has unicode characters: 你好,世界!",
|
||||
],
|
||||
)
|
||||
def test_return_history_as_string_with_different_contents(content):
|
||||
conv = Conversation()
|
||||
conv.add("user", content)
|
||||
result = conv.return_history_as_string()
|
||||
expected = f"user: {content}\n\n"
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_return_history_as_string_with_large_message(conversation):
|
||||
large_message = "Hello, world! " * 10000 # 10,000 repetitions
|
||||
conversation.add("user", large_message)
|
||||
result = conversation.return_history_as_string()
|
||||
expected = (
|
||||
"user: Hello, world!\n\nassistant: Hello, user!\n\nuser:"
|
||||
f" {large_message}\n\n"
|
||||
)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_search_keyword_in_conversation(conversation):
|
||||
result = conversation.search_keyword_in_conversation("Hello")
|
||||
assert len(result) == 2
|
||||
assert result[0]["content"] == "Hello, world!"
|
||||
assert result[1]["content"] == "Hello, user!"
|
||||
|
||||
|
||||
def test_export_import_conversation(conversation, tmp_path):
|
||||
filename = tmp_path / "conversation.txt"
|
||||
conversation.export_conversation(filename)
|
||||
new_conversation = Conversation()
|
||||
new_conversation.import_conversation(filename)
|
||||
assert (
|
||||
new_conversation.return_history_as_string()
|
||||
== conversation.return_history_as_string()
|
||||
)
|
||||
|
||||
|
||||
def test_count_messages_by_role(conversation):
|
||||
counts = conversation.count_messages_by_role()
|
||||
assert counts["user"] == 1
|
||||
assert counts["assistant"] == 1
|
||||
|
||||
|
||||
def test_display_conversation(capsys, conversation):
|
||||
conversation.display_conversation()
|
||||
captured = capsys.readouterr()
|
||||
assert "user: Hello, world!\n\n" in captured.out
|
||||
assert "assistant: Hello, user!\n\n" in captured.out
|
||||
|
||||
|
||||
def test_display_conversation_detailed(capsys, conversation):
|
||||
conversation.display_conversation(detailed=True)
|
||||
captured = capsys.readouterr()
|
||||
assert "user: Hello, world!\n\n" in captured.out
|
||||
assert "assistant: Hello, user!\n\n" in captured.out
|
||||
|
||||
|
||||
def test_search():
|
||||
conv = Conversation()
|
||||
conv.add("user", "Hello, world!")
|
||||
conv.add("assistant", "Hello, user!")
|
||||
results = conv.search("Hello")
|
||||
assert len(results) == 2
|
||||
assert results[0]["content"] == "Hello, world!"
|
||||
assert results[1]["content"] == "Hello, user!"
|
||||
|
||||
|
||||
def test_return_history_as_string():
|
||||
conv = Conversation()
|
||||
conv.add("user", "Hello, world!")
|
||||
conv.add("assistant", "Hello, user!")
|
||||
result = conv.return_history_as_string()
|
||||
expected = "user: Hello, world!\n\nassistant: Hello, user!\n\n"
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_search_no_results():
|
||||
conv = Conversation()
|
||||
conv.add("user", "Hello, world!")
|
||||
conv.add("assistant", "Hello, user!")
|
||||
results = conv.search("Goodbye")
|
||||
assert len(results) == 0
|
||||
|
||||
|
||||
def test_search_case_insensitive():
|
||||
conv = Conversation()
|
||||
conv.add("user", "Hello, world!")
|
||||
conv.add("assistant", "Hello, user!")
|
||||
results = conv.search("hello")
|
||||
assert len(results) == 2
|
||||
assert results[0]["content"] == "Hello, world!"
|
||||
assert results[1]["content"] == "Hello, user!"
|
||||
|
||||
|
||||
def test_search_multiple_occurrences():
|
||||
conv = Conversation()
|
||||
conv.add("user", "Hello, world! Hello, world!")
|
||||
conv.add("assistant", "Hello, user!")
|
||||
results = conv.search("Hello")
|
||||
assert len(results) == 2
|
||||
assert results[0]["content"] == "Hello, world! Hello, world!"
|
||||
assert results[1]["content"] == "Hello, user!"
|
||||
|
||||
|
||||
def test_query_no_results():
|
||||
conv = Conversation()
|
||||
conv.add("user", "Hello, world!")
|
||||
conv.add("assistant", "Hello, user!")
|
||||
results = conv.query("Goodbye")
|
||||
assert len(results) == 0
|
||||
|
||||
|
||||
def test_query_case_insensitive():
|
||||
conv = Conversation()
|
||||
conv.add("user", "Hello, world!")
|
||||
conv.add("assistant", "Hello, user!")
|
||||
results = conv.query("hello")
|
||||
assert len(results) == 2
|
||||
assert results[0]["content"] == "Hello, world!"
|
||||
assert results[1]["content"] == "Hello, user!"
|
||||
|
||||
|
||||
def test_query_multiple_occurrences():
|
||||
conv = Conversation()
|
||||
conv.add("user", "Hello, world! Hello, world!")
|
||||
conv.add("assistant", "Hello, user!")
|
||||
results = conv.query("Hello")
|
||||
assert len(results) == 2
|
||||
assert results[0]["content"] == "Hello, world! Hello, world!"
|
||||
assert results[1]["content"] == "Hello, user!"
|
Loading…
Reference in new issue