Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions examples/conversation_cache_interactive.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""
Interactive agent to manually test conversation string caching.
Run: python examples/conversation_cache_interactive.py
Type messages, see cache stats update after each response.
"""

from swarms import Agent

agent = Agent(
agent_name="CacheTestAgent",
model_name="claude-sonnet-4-5",
max_loops=1,
verbose=False,
temperature=1.0,
)

print("\n=== Conversation Cache Interactive Test ===")
print("Type your messages. After each response, cache stats are shown.")
print("Type 'quit' to exit.\n")

while True:
user_input = input("You: ").strip()
if user_input.lower() in ("quit", "exit", "q"):
break
if not user_input:
continue

response = agent.run(user_input)
print(f"\nAgent: {response}\n")

# Call get_str() multiple times to exercise the cache
agent.short_memory.get_str()
agent.short_memory.get_str()
agent.short_memory.get_str()

stats = agent.short_memory.get_cache_stats()
print(f"--- Cache Stats ---")
print(f" Hits: {stats['hits']} (get_str() returned cached string)")
print(f" Misses: {stats['misses']} (get_str() rebuilt the string)")
print(f" Hit rate: {stats['hit_rate']:.0%}")
print(f" Cached tokens: {stats['cached_tokens']}")
print(f"-------------------\n")
72 changes: 70 additions & 2 deletions swarms/structs/conversation.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ def __init__(
export_method: str = "json",
dynamic_context_window: bool = True,
caching: bool = True,
cache_enabled: bool = True,
output_metadata: bool = False,
):

Expand All @@ -117,13 +118,16 @@ def __init__(
self.token_count = token_count
self.export_method = export_method
self.dynamic_context_window = dynamic_context_window
self.caching = caching
self.caching = caching or cache_enabled
self.output_metadata = output_metadata

if self.name is None:
self.name = id

self.conversation_history = []
self._str_cache: Optional[str] = None
self._cache_hits: int = 0
self._cache_misses: int = 0

self.setup_file_path()
self.setup()
Expand Down Expand Up @@ -264,6 +268,7 @@ def add_in_memory(

# Add message to conversation history
self.conversation_history.append(message)
self._str_cache = None

# Handle token counting in a separate thread if enabled
if self.token_count is True:
Expand Down Expand Up @@ -413,6 +418,7 @@ def add_multiple(
def delete(self, index: str):
"""Delete a message from the conversation history."""
self.conversation_history.pop(int(index))
self._str_cache = None

def update(self, index: str, role, content):
"""Update a message in the conversation history.
Expand All @@ -425,6 +431,7 @@ def update(self, index: str, role, content):
if 0 <= int(index) < len(self.conversation_history):
self.conversation_history[int(index)]["role"] = role
self.conversation_history[int(index)]["content"] = content
self._str_cache = None
else:
logger.warning(f"Invalid index: {index}")

Expand Down Expand Up @@ -533,7 +540,37 @@ def get_str(self) -> str:
Returns:
str: The conversation history.
"""
return self.return_history_as_string()
if self._str_cache is None:
self._cache_misses += 1
self._str_cache = self.return_history_as_string()
else:
self._cache_hits += 1
return self._str_cache

def get_cache_stats(self) -> Dict[str, Any]:
"""Return cache performance statistics for get_str().

Returns:
Dict[str, Any]: A dictionary with hits, misses, cached_tokens,
total_tokens, and hit_rate.
"""
total_calls = self._cache_hits + self._cache_misses
cached_tokens = (
count_tokens(self._str_cache, self.tokenizer_model_name)
if self._str_cache
else 0
)
return {
"hits": self._cache_hits,
"misses": self._cache_misses,
"cached_tokens": cached_tokens,
"total_tokens": total_calls * cached_tokens,
"hit_rate": (
self._cache_hits / total_calls
if total_calls > 0
else 0.0
),
}

def to_dict(self) -> Dict[Any, Any]:
"""
Expand Down Expand Up @@ -693,6 +730,7 @@ def load_from_json(self, filename: str):
self.conversation_history = data.get(
"conversation_history", []
)
self._str_cache = None

logger.info(
f"Successfully loaded conversation from {filename}"
Expand Down Expand Up @@ -725,6 +763,7 @@ def load_from_yaml(self, filename: str):
self.conversation_history = data.get(
"conversation_history", []
)
self._str_cache = None

logger.info(
f"Successfully loaded conversation from {filename}"
Expand Down Expand Up @@ -841,6 +880,7 @@ def truncate_memory_with_tokenizer(self):

# Update conversation history
self.conversation_history = truncated_history
self._str_cache = None

def _binary_search_truncate(
self, text, target_tokens, model_name
Expand Down Expand Up @@ -902,6 +942,7 @@ def _binary_search_truncate(
def clear(self):
"""Clear the conversation history."""
self.conversation_history = []
self._str_cache = None

def to_json(self):
"""Convert the conversation history to a JSON string.
Expand All @@ -911,6 +952,14 @@ def to_json(self):
"""
return json.dumps(self.conversation_history)

def to_yaml(self):
"""Convert the conversation history to a YAML string.

Returns:
str: The conversation history as a YAML string.
"""
return yaml.dump(self.conversation_history)

def to_list(self):
"""Convert the conversation history to a list.

Expand Down Expand Up @@ -1050,6 +1099,7 @@ def batch_add(self, messages: List[dict]):
messages (List[dict]): List of messages to add.
"""
self.conversation_history.extend(messages)
self._str_cache = None

@classmethod
def load_conversation(
Expand Down Expand Up @@ -1174,9 +1224,27 @@ def list_conversations(
conversations, key=lambda x: x["created_at"], reverse=True
)

@classmethod
def list_cached_conversations(
cls, conversations_dir: Optional[str] = None
) -> List[str]:
"""List names of all saved conversations.

Args:
conversations_dir (Optional[str]): Directory containing conversations.

Returns:
List[str]: List of conversation names.
"""
return [
c["name"]
for c in cls.list_conversations(conversations_dir)
]

def clear_memory(self):
"""Clear the memory of the conversation."""
self.conversation_history = []
self._str_cache = None

def _dynamic_auto_chunking_worker(self):
"""
Expand Down
Loading