Skip to content

Commit d0fa48b

Browse files
committed
Release v4.5.33
1 parent 0d2c1bd commit d0fa48b

File tree

12 files changed

+204
-18
lines changed

12 files changed

+204
-18
lines changed

docker/Dockerfile.chat

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
1616
# Install Python packages (using latest versions)
1717
RUN pip install --no-cache-dir \
1818
praisonai_tools \
19-
"praisonai>=4.5.32" \
19+
"praisonai>=4.5.33" \
2020
"praisonai[chat]" \
2121
"embedchain[github,youtube]"
2222

docker/Dockerfile.dev

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ RUN mkdir -p /root/.praison
2020
# Install Python packages (using latest versions)
2121
RUN pip install --no-cache-dir \
2222
praisonai_tools \
23-
"praisonai>=4.5.32" \
23+
"praisonai>=4.5.33" \
2424
"praisonai[ui]" \
2525
"praisonai[chat]" \
2626
"praisonai[realtime]" \

docker/Dockerfile.ui

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ RUN mkdir -p /root/.praison
1616
# Install Python packages (using latest versions)
1717
RUN pip install --no-cache-dir \
1818
praisonai_tools \
19-
"praisonai>=4.5.32" \
19+
"praisonai>=4.5.33" \
2020
"praisonai[ui]" \
2121
"praisonai[crewai]"
2222

src/praisonai-agents/praisonaiagents/agent/agent.py

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4863,7 +4863,14 @@ def _execute_mcp_tool(mcp_instance, func_name, args):
48634863
return {"error": error_msg}
48644864

48654865
def clear_history(self):
4866+
"""Clear all chat history.
4867+
4868+
Also resets _auto_save_last_index to prevent silent message loss
4869+
when auto_save is enabled.
4870+
"""
48664871
self.chat_history = []
4872+
# Reset auto-save index to prevent stale index causing message loss
4873+
self._auto_save_last_index = 0
48674874

48684875
# -------------------------------------------------------------------------
48694876
# History Management Methods
@@ -4888,6 +4895,8 @@ def prune_history(self, keep_last: int = 5) -> int:
48884895

48894896
deleted_count = len(self.chat_history) - keep_last
48904897
self.chat_history = self.chat_history[-keep_last:]
4898+
# Reset auto-save index to match new history length
4899+
self._auto_save_last_index = len(self.chat_history)
48914900
return deleted_count
48924901

48934902
def delete_history(self, index: int) -> bool:
@@ -4905,6 +4914,12 @@ def delete_history(self, index: int) -> bool:
49054914
with self._history_lock:
49064915
try:
49074916
del self.chat_history[index]
4917+
# Adjust auto-save index if deletion affects saved range
4918+
if hasattr(self, '_auto_save_last_index'):
4919+
self._auto_save_last_index = min(
4920+
self._auto_save_last_index,
4921+
len(self.chat_history)
4922+
)
49084923
return True
49094924
except IndexError:
49104925
return False
@@ -4927,7 +4942,14 @@ def delete_history_matching(self, pattern: str) -> int:
49274942
msg for msg in self.chat_history
49284943
if pattern.lower() not in msg.get("content", "").lower()
49294944
]
4930-
return original_len - len(self.chat_history)
4945+
deleted_count = original_len - len(self.chat_history)
4946+
# Adjust auto-save index if deletion affects saved range
4947+
if deleted_count > 0 and hasattr(self, '_auto_save_last_index'):
4948+
self._auto_save_last_index = min(
4949+
self._auto_save_last_index,
4950+
len(self.chat_history)
4951+
)
4952+
return deleted_count
49314953

49324954
def get_history_size(self) -> int:
49334955
"""Get the current number of messages in chat history."""
@@ -7213,6 +7235,9 @@ def _auto_save_session(self):
72137235
G-1 FIX: Routes to SessionStore instead of Memory.save_session() to
72147236
maintain clean separation between conversation history (SessionStore)
72157237
and semantic memory (Memory).
7238+
7239+
Issue 1 FIX: Track last saved index to avoid duplicate insertion when
7240+
called multiple times in the same session.
72167241
"""
72177242
if not self.auto_save:
72187243
return
@@ -7224,19 +7249,30 @@ def _auto_save_session(self):
72247249
for msg in self.chat_history
72257250
]
72267251

7252+
# Issue 1 FIX: Only save NEW messages since last save
7253+
# Track last saved index to avoid duplicates
7254+
last_saved = getattr(self, '_auto_save_last_index', 0)
7255+
new_messages = clean_history[last_saved:]
7256+
7257+
if not new_messages:
7258+
return # Nothing new to save
7259+
72277260
# G-1 FIX: Use SessionStore for conversation history persistence
72287261
# This maintains clean separation: Memory = facts, SessionStore = turns
72297262
if self._session_store is not None:
7230-
# Persist each message to SessionStore
7231-
for msg in clean_history:
7263+
# Persist only NEW messages to SessionStore
7264+
for msg in new_messages:
72327265
self._session_store.add_message(
72337266
self.auto_save, # Use auto_save name as session_id
72347267
role=msg.get("role", "user"),
72357268
content=msg.get("content", ""),
72367269
)
7237-
logging.debug(f"Auto-saved session to SessionStore: {self.auto_save}")
7270+
# Update last saved index
7271+
self._auto_save_last_index = len(clean_history)
7272+
logging.debug(f"Auto-saved {len(new_messages)} new messages to SessionStore: {self.auto_save}")
72387273
elif self._memory_instance and hasattr(self._memory_instance, 'save_session'):
72397274
# Fallback to Memory.save_session() for backward compatibility
7275+
# Memory.save_session() replaces entire history, so no duplicate issue
72407276
self._memory_instance.save_session(
72417277
name=self.auto_save,
72427278
conversation_history=clean_history,

src/praisonai-agents/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "praisonaiagents"
7-
version = "1.5.32"
7+
version = "1.5.33"
88
description = "Praison AI agents for completing complex tasks with Self Reflection Agents"
99
readme = "README.md"
1010
requires-python = ">=3.10"

src/praisonai-agents/tests/unit/test_memory_history_separation.py

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,156 @@ def test_save_agent_histories_routes_to_session_store(self):
111111
assert "SessionStore" in source, "Method should reference SessionStore"
112112
assert "get_default_session_store" in source, "Method should try to get SessionStore"
113113

114+
def test_save_agent_histories_behavioral(self):
115+
"""Issue 3 FIX: Behavioral test - verify actual routing to SessionStore."""
116+
from praisonaiagents.session import Session
117+
118+
# Create session
119+
session = Session(session_id="test_behavioral", user_id="test_user")
120+
121+
# Create mock agent with chat history
122+
mock_agent = MagicMock()
123+
mock_agent.chat_history = [
124+
{"role": "user", "content": "Hello"},
125+
{"role": "assistant", "content": "Hi!"},
126+
]
127+
128+
# Add agent to session
129+
session._agents["test_agent"] = {
130+
"agent": mock_agent,
131+
"chat_history": []
132+
}
133+
134+
# Call the method - it should not raise an exception
135+
# This tests the actual behavior, not just source code
136+
session._save_agent_chat_histories()
137+
138+
139+
class TestIssue1DuplicatePrevention:
140+
"""Issue 1: Verify duplicate history insertion is prevented."""
141+
142+
def test_auto_save_tracks_last_saved_index(self):
143+
"""Verify _auto_save_session tracks last saved index to prevent duplicates."""
144+
from praisonaiagents import Agent
145+
146+
agent = Agent(
147+
name="test",
148+
instructions="Test agent",
149+
auto_save="test_session",
150+
)
151+
152+
# Mock session store
153+
mock_session_store = MagicMock()
154+
agent._session_store = mock_session_store
155+
156+
# Add initial chat history
157+
agent.chat_history = [
158+
{"role": "user", "content": "Hello"},
159+
{"role": "assistant", "content": "Hi there!"},
160+
]
161+
162+
# First save - should save 2 messages
163+
agent._auto_save_session()
164+
assert mock_session_store.add_message.call_count == 2
165+
166+
# Second save with same history - should save 0 new messages
167+
mock_session_store.reset_mock()
168+
agent._auto_save_session()
169+
assert mock_session_store.add_message.call_count == 0, "Should not re-save existing messages"
170+
171+
# Add new message
172+
agent.chat_history.append({"role": "user", "content": "How are you?"})
173+
174+
# Third save - should save only 1 new message
175+
mock_session_store.reset_mock()
176+
agent._auto_save_session()
177+
assert mock_session_store.add_message.call_count == 1, "Should only save new message"
178+
179+
180+
class TestEdgeCaseClearHistoryResetIndex:
181+
"""Edge case: clear_history() must reset _auto_save_last_index."""
182+
183+
def test_clear_history_resets_auto_save_index(self):
184+
"""Verify clear_history() resets _auto_save_last_index to prevent silent message loss."""
185+
from praisonaiagents import Agent
186+
187+
agent = Agent(
188+
name="test",
189+
instructions="Test agent",
190+
auto_save="test_session",
191+
)
192+
193+
mock_store = MagicMock()
194+
agent._session_store = mock_store
195+
196+
# Save initial messages
197+
agent.chat_history = [{"role": "user", "content": "msg1"}]
198+
agent._auto_save_session()
199+
assert mock_store.add_message.call_count == 1
200+
assert agent._auto_save_last_index == 1
201+
202+
# Clear history - index should reset
203+
agent.clear_history()
204+
assert agent._auto_save_last_index == 0, "Index should reset to 0 after clear_history()"
205+
206+
# Add new message after clear
207+
agent.chat_history = [{"role": "user", "content": "msg2"}]
208+
mock_store.reset_mock()
209+
agent._auto_save_session()
210+
211+
# Should save the new message (not skip due to stale index)
212+
assert mock_store.add_message.call_count == 1, "New message should be saved after clear"
213+
214+
def test_prune_history_adjusts_auto_save_index(self):
215+
"""Verify prune_history() adjusts _auto_save_last_index."""
216+
from praisonaiagents import Agent
217+
218+
agent = Agent(
219+
name="test",
220+
instructions="Test agent",
221+
auto_save="test_session",
222+
)
223+
224+
mock_store = MagicMock()
225+
agent._session_store = mock_store
226+
227+
# Save 5 messages
228+
agent.chat_history = [
229+
{"role": "user", "content": f"msg{i}"} for i in range(5)
230+
]
231+
agent._auto_save_session()
232+
assert agent._auto_save_last_index == 5
233+
234+
# Prune to keep last 2
235+
agent.prune_history(keep_last=2)
236+
assert len(agent.chat_history) == 2
237+
assert agent._auto_save_last_index == 2, "Index should match pruned history length"
238+
239+
def test_delete_history_adjusts_auto_save_index(self):
240+
"""Verify delete_history() adjusts _auto_save_last_index."""
241+
from praisonaiagents import Agent
242+
243+
agent = Agent(
244+
name="test",
245+
instructions="Test agent",
246+
auto_save="test_session",
247+
)
248+
249+
mock_store = MagicMock()
250+
agent._session_store = mock_store
251+
252+
# Save 3 messages
253+
agent.chat_history = [
254+
{"role": "user", "content": f"msg{i}"} for i in range(3)
255+
]
256+
agent._auto_save_session()
257+
assert agent._auto_save_last_index == 3
258+
259+
# Delete one message
260+
agent.delete_history(0)
261+
assert len(agent.chat_history) == 2
262+
assert agent._auto_save_last_index == 2, "Index should be adjusted after deletion"
263+
114264

115265
class TestMemoryHistorySeparation:
116266
"""Test that Memory and History are properly separated."""

src/praisonai-agents/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/praisonai/praisonai.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ class Praisonai < Formula
33

44
desc "AI tools for various AI applications"
55
homepage "https://github.com/MervinPraison/PraisonAI"
6-
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v4.5.32.tar.gz"
7-
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v4.5.32.tar.gz | shasum -a 256`.split.first
6+
url "https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v4.5.33.tar.gz"
7+
sha256 `curl -sL https://github.com/MervinPraison/PraisonAI/archive/refs/tags/v4.5.33.tar.gz | shasum -a 256`.split.first
88
license "MIT"
99

1010
depends_on "python@3.11"

src/praisonai/praisonai/deploy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def create_dockerfile(self):
5757
file.write("FROM python:3.11-slim\n")
5858
file.write("WORKDIR /app\n")
5959
file.write("COPY . .\n")
60-
file.write("RUN pip install flask praisonai==4.5.32 gunicorn markdown\n")
60+
file.write("RUN pip install flask praisonai==4.5.33 gunicorn markdown\n")
6161
file.write("EXPOSE 8080\n")
6262
file.write('CMD ["gunicorn", "-b", "0.0.0.0:8080", "api:app"]\n')
6363

src/praisonai/praisonai/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "4.5.32"
1+
__version__ = "4.5.33"

0 commit comments

Comments
 (0)