rakam-systems-agent 0.1.1rc7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rakam_systems_agent/__init__.py +35 -0
- rakam_systems_agent/components/__init__.py +26 -0
- rakam_systems_agent/components/base_agent.py +358 -0
- rakam_systems_agent/components/chat_history/__init__.py +10 -0
- rakam_systems_agent/components/chat_history/json_chat_history.py +372 -0
- rakam_systems_agent/components/chat_history/postgres_chat_history.py +668 -0
- rakam_systems_agent/components/chat_history/sql_chat_history.py +446 -0
- rakam_systems_agent/components/llm_gateway/README.md +505 -0
- rakam_systems_agent/components/llm_gateway/__init__.py +16 -0
- rakam_systems_agent/components/llm_gateway/gateway_factory.py +313 -0
- rakam_systems_agent/components/llm_gateway/mistral_gateway.py +287 -0
- rakam_systems_agent/components/llm_gateway/openai_gateway.py +295 -0
- rakam_systems_agent/components/tools/LLM_GATEWAY_TOOLS_README.md +533 -0
- rakam_systems_agent/components/tools/__init__.py +46 -0
- rakam_systems_agent/components/tools/example_tools.py +431 -0
- rakam_systems_agent/components/tools/llm_gateway_tools.py +605 -0
- rakam_systems_agent/components/tools/search_tool.py +14 -0
- rakam_systems_agent/server/README.md +375 -0
- rakam_systems_agent/server/__init__.py +12 -0
- rakam_systems_agent/server/mcp_server_agent.py +127 -0
- rakam_systems_agent-0.1.1rc7.dist-info/METADATA +367 -0
- rakam_systems_agent-0.1.1rc7.dist-info/RECORD +23 -0
- rakam_systems_agent-0.1.1rc7.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
"""JSON-Based Chat History Manager.
|
|
2
|
+
|
|
3
|
+
This module provides a ChatHistoryComponent implementation that stores
|
|
4
|
+
chat history in a JSON file. Suitable for development, testing, and
|
|
5
|
+
single-instance deployments.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
from typing import Any, Dict, List, Optional
|
|
13
|
+
|
|
14
|
+
from rakam_systems_core.ai_core.interfaces.chat_history import \
|
|
15
|
+
ChatHistoryComponent
|
|
16
|
+
|
|
17
|
+
# Optional pydantic-ai integration
|
|
18
|
+
try:
|
|
19
|
+
from pydantic_ai.messages import ModelMessage, ModelMessagesTypeAdapter
|
|
20
|
+
from pydantic_core import to_jsonable_python
|
|
21
|
+
PYDANTIC_AI_AVAILABLE = True
|
|
22
|
+
except ImportError:
|
|
23
|
+
PYDANTIC_AI_AVAILABLE = False
|
|
24
|
+
ModelMessagesTypeAdapter = None # type: ignore
|
|
25
|
+
ModelMessage = None # type: ignore
|
|
26
|
+
to_jsonable_python = None # type: ignore
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class JSONChatHistory(ChatHistoryComponent):
|
|
30
|
+
"""Chat history manager using JSON file storage.
|
|
31
|
+
|
|
32
|
+
This implementation stores all chat histories in a single JSON file.
|
|
33
|
+
It's suitable for:
|
|
34
|
+
- Development and testing
|
|
35
|
+
- Single-instance deployments
|
|
36
|
+
- Small to medium scale applications
|
|
37
|
+
|
|
38
|
+
For production with multiple instances, consider using a database-backed
|
|
39
|
+
implementation instead.
|
|
40
|
+
|
|
41
|
+
Config options:
|
|
42
|
+
storage_path: Path to the JSON file (default: "./chat_history.json")
|
|
43
|
+
auto_save: Whether to save after each modification (default: True)
|
|
44
|
+
indent: JSON indentation for readability (default: 4)
|
|
45
|
+
|
|
46
|
+
Example:
|
|
47
|
+
>>> history = JSONChatHistory(config={"storage_path": "./data/chats.json"})
|
|
48
|
+
>>> history.add_message("chat123", {"role": "user", "content": "Hello"})
|
|
49
|
+
>>> history.add_message("chat123", {"role": "assistant", "content": "Hi there!"})
|
|
50
|
+
>>> messages = history.get_chat_history("chat123")
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
def __init__(
|
|
54
|
+
self,
|
|
55
|
+
name: str = "json_chat_history",
|
|
56
|
+
config: Optional[Dict[str, Any]] = None,
|
|
57
|
+
storage_path: Optional[str] = None,
|
|
58
|
+
) -> None:
|
|
59
|
+
"""Initialize the JSON chat history manager.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
name: Component name for identification.
|
|
63
|
+
config: Configuration dictionary. Supports:
|
|
64
|
+
- storage_path: Path to JSON file
|
|
65
|
+
- auto_save: Save after each modification (default: True)
|
|
66
|
+
- indent: JSON indentation (default: 4)
|
|
67
|
+
storage_path: Direct path override (takes precedence over config).
|
|
68
|
+
"""
|
|
69
|
+
super().__init__(name, config)
|
|
70
|
+
|
|
71
|
+
# Get storage path from argument, config, or default
|
|
72
|
+
self.storage_path = storage_path or self.config.get(
|
|
73
|
+
"storage_path", "./chat_history.json"
|
|
74
|
+
)
|
|
75
|
+
self.auto_save = self.config.get("auto_save", True)
|
|
76
|
+
self.indent = self.config.get("indent", 4)
|
|
77
|
+
|
|
78
|
+
# In-memory cache of chat history
|
|
79
|
+
self._chat_history: Dict[str, List[Dict[str, Any]]] = {}
|
|
80
|
+
|
|
81
|
+
def setup(self) -> None:
|
|
82
|
+
"""Initialize storage and load existing history."""
|
|
83
|
+
self._initialize_storage()
|
|
84
|
+
super().setup()
|
|
85
|
+
|
|
86
|
+
def shutdown(self) -> None:
|
|
87
|
+
"""Save and cleanup resources."""
|
|
88
|
+
self._save()
|
|
89
|
+
super().shutdown()
|
|
90
|
+
|
|
91
|
+
def _initialize_storage(self) -> None:
|
|
92
|
+
"""Initialize storage: create directory and load existing data."""
|
|
93
|
+
# Ensure directory exists
|
|
94
|
+
storage_dir = os.path.dirname(self.storage_path)
|
|
95
|
+
if storage_dir:
|
|
96
|
+
os.makedirs(storage_dir, exist_ok=True)
|
|
97
|
+
|
|
98
|
+
# Load existing data or create new file
|
|
99
|
+
if os.path.exists(self.storage_path):
|
|
100
|
+
try:
|
|
101
|
+
with open(self.storage_path, 'r', encoding='utf-8') as f:
|
|
102
|
+
content = f.read().strip()
|
|
103
|
+
if content:
|
|
104
|
+
self._chat_history = json.loads(content)
|
|
105
|
+
else:
|
|
106
|
+
self._chat_history = {}
|
|
107
|
+
except (json.JSONDecodeError, IOError):
|
|
108
|
+
# Invalid JSON or IO error - start fresh
|
|
109
|
+
self._chat_history = {}
|
|
110
|
+
self._save()
|
|
111
|
+
else:
|
|
112
|
+
# Create new file
|
|
113
|
+
self._chat_history = {}
|
|
114
|
+
self._save()
|
|
115
|
+
|
|
116
|
+
def _save(self) -> None:
|
|
117
|
+
"""Save current chat history to JSON file."""
|
|
118
|
+
try:
|
|
119
|
+
with open(self.storage_path, 'w', encoding='utf-8') as f:
|
|
120
|
+
json.dump(self._chat_history, f,
|
|
121
|
+
indent=self.indent, ensure_ascii=False)
|
|
122
|
+
except IOError as e:
|
|
123
|
+
raise IOError(
|
|
124
|
+
f"Failed to save chat history to {self.storage_path}: {e}")
|
|
125
|
+
|
|
126
|
+
def _ensure_initialized(self) -> None:
|
|
127
|
+
"""Ensure the component is initialized before operations."""
|
|
128
|
+
if not self.initialized:
|
|
129
|
+
self.setup()
|
|
130
|
+
|
|
131
|
+
def add_message(self, chat_id: str, message: Dict[str, Any]) -> None:
|
|
132
|
+
"""Add a single message to a chat session.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
chat_id: Unique identifier for the chat session.
|
|
136
|
+
message: Message object (dict with role, content, timestamp, etc.).
|
|
137
|
+
"""
|
|
138
|
+
self._ensure_initialized()
|
|
139
|
+
|
|
140
|
+
if chat_id not in self._chat_history:
|
|
141
|
+
self._chat_history[chat_id] = []
|
|
142
|
+
|
|
143
|
+
self._chat_history[chat_id].append(message)
|
|
144
|
+
|
|
145
|
+
if self.auto_save:
|
|
146
|
+
self._save()
|
|
147
|
+
|
|
148
|
+
def set_messages(self, chat_id: str, messages: List[Dict[str, Any]]) -> None:
|
|
149
|
+
"""Set/replace all messages for a chat session.
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
chat_id: Unique identifier for the chat session.
|
|
153
|
+
messages: List of message objects to store.
|
|
154
|
+
"""
|
|
155
|
+
self._ensure_initialized()
|
|
156
|
+
# Copy to avoid reference issues
|
|
157
|
+
self._chat_history[chat_id] = list(messages)
|
|
158
|
+
|
|
159
|
+
if self.auto_save:
|
|
160
|
+
self._save()
|
|
161
|
+
|
|
162
|
+
def get_chat_history(self, chat_id: str) -> List[Dict[str, Any]]:
|
|
163
|
+
"""Retrieve all messages for a chat session.
|
|
164
|
+
|
|
165
|
+
Args:
|
|
166
|
+
chat_id: Unique identifier for the chat session.
|
|
167
|
+
|
|
168
|
+
Returns:
|
|
169
|
+
List of message objects, or empty list if chat doesn't exist.
|
|
170
|
+
"""
|
|
171
|
+
self._ensure_initialized()
|
|
172
|
+
return self._chat_history.get(chat_id, [])
|
|
173
|
+
|
|
174
|
+
def get_all_chat_ids(self) -> List[str]:
|
|
175
|
+
"""Get all chat IDs currently stored.
|
|
176
|
+
|
|
177
|
+
Returns:
|
|
178
|
+
List of all chat session identifiers.
|
|
179
|
+
"""
|
|
180
|
+
self._ensure_initialized()
|
|
181
|
+
return list(self._chat_history.keys())
|
|
182
|
+
|
|
183
|
+
def delete_chat_history(self, chat_id: str) -> bool:
|
|
184
|
+
"""Delete all messages for a chat session.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
chat_id: Unique identifier for the chat session to delete.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
True if deletion was successful, False if chat_id didn't exist.
|
|
191
|
+
"""
|
|
192
|
+
self._ensure_initialized()
|
|
193
|
+
|
|
194
|
+
if chat_id not in self._chat_history:
|
|
195
|
+
return False
|
|
196
|
+
|
|
197
|
+
del self._chat_history[chat_id]
|
|
198
|
+
|
|
199
|
+
if self.auto_save:
|
|
200
|
+
self._save()
|
|
201
|
+
|
|
202
|
+
return True
|
|
203
|
+
|
|
204
|
+
def clear_all(self) -> None:
|
|
205
|
+
"""Delete all chat histories."""
|
|
206
|
+
self._ensure_initialized()
|
|
207
|
+
self._chat_history = {}
|
|
208
|
+
|
|
209
|
+
if self.auto_save:
|
|
210
|
+
self._save()
|
|
211
|
+
|
|
212
|
+
def get_readable_chat_history(
|
|
213
|
+
self,
|
|
214
|
+
chat_id: str,
|
|
215
|
+
user_role: str = "user",
|
|
216
|
+
assistant_role: str = "assistant",
|
|
217
|
+
) -> List[Dict[str, Any]]:
|
|
218
|
+
"""Get chat history in a human-readable format.
|
|
219
|
+
|
|
220
|
+
This method transforms the raw message format into a display-friendly
|
|
221
|
+
format with 'from', 'message', and optional 'timestamp' keys.
|
|
222
|
+
|
|
223
|
+
Args:
|
|
224
|
+
chat_id: Unique identifier for the chat session.
|
|
225
|
+
user_role: The role name for user messages (default: "user").
|
|
226
|
+
assistant_role: The role name for assistant messages (default: "assistant").
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
List of formatted message dictionaries with:
|
|
230
|
+
- 'from': "user" or "assistant"
|
|
231
|
+
- 'message': The message content
|
|
232
|
+
- 'timestamp': Message timestamp (if available)
|
|
233
|
+
"""
|
|
234
|
+
self._ensure_initialized()
|
|
235
|
+
|
|
236
|
+
messages = self.get_chat_history(chat_id)
|
|
237
|
+
readable_messages = []
|
|
238
|
+
|
|
239
|
+
for msg in messages:
|
|
240
|
+
role = msg.get("role", "")
|
|
241
|
+
content = msg.get("content", "")
|
|
242
|
+
timestamp = msg.get("timestamp")
|
|
243
|
+
|
|
244
|
+
# Determine the 'from' field based on role
|
|
245
|
+
if role == user_role:
|
|
246
|
+
from_field = "user"
|
|
247
|
+
elif role == assistant_role:
|
|
248
|
+
from_field = "assistant"
|
|
249
|
+
else:
|
|
250
|
+
# Skip system messages or unknown roles
|
|
251
|
+
continue
|
|
252
|
+
|
|
253
|
+
formatted = {
|
|
254
|
+
"from": from_field,
|
|
255
|
+
"message": content,
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if timestamp:
|
|
259
|
+
formatted["timestamp"] = timestamp
|
|
260
|
+
|
|
261
|
+
readable_messages.append(formatted)
|
|
262
|
+
|
|
263
|
+
return readable_messages
|
|
264
|
+
|
|
265
|
+
def save(self) -> None:
|
|
266
|
+
"""Manually save the current state to disk.
|
|
267
|
+
|
|
268
|
+
Useful when auto_save is disabled.
|
|
269
|
+
"""
|
|
270
|
+
self._ensure_initialized()
|
|
271
|
+
self._save()
|
|
272
|
+
|
|
273
|
+
def reload(self) -> None:
|
|
274
|
+
"""Reload chat history from disk, discarding in-memory changes."""
|
|
275
|
+
self._initialize_storage()
|
|
276
|
+
|
|
277
|
+
# ==================== Pydantic-AI Integration ====================
|
|
278
|
+
|
|
279
|
+
def get_message_history(self, chat_id: str) -> Optional[List[Any]]:
|
|
280
|
+
"""Get chat history in pydantic-ai compatible format.
|
|
281
|
+
|
|
282
|
+
This method converts the stored JSON history to pydantic-ai's
|
|
283
|
+
ModelMessage format, ready to be passed to agent.run() or
|
|
284
|
+
agent.run_stream() as message_history.
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
chat_id: Unique identifier for the chat session.
|
|
288
|
+
|
|
289
|
+
Returns:
|
|
290
|
+
List of ModelMessage objects for pydantic-ai, or None if:
|
|
291
|
+
- Chat doesn't exist or is empty
|
|
292
|
+
- pydantic-ai is not installed
|
|
293
|
+
|
|
294
|
+
Example:
|
|
295
|
+
>>> history = JSONChatHistory()
|
|
296
|
+
>>> message_history = history.get_message_history("chat123")
|
|
297
|
+
>>> result = await agent.run("Hello", message_history=message_history)
|
|
298
|
+
"""
|
|
299
|
+
if not PYDANTIC_AI_AVAILABLE:
|
|
300
|
+
raise ImportError(
|
|
301
|
+
"pydantic-ai is not installed. Install it with: pip install pydantic-ai"
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
self._ensure_initialized()
|
|
305
|
+
raw_history = self._chat_history.get(chat_id, [])
|
|
306
|
+
|
|
307
|
+
if not raw_history:
|
|
308
|
+
return None
|
|
309
|
+
|
|
310
|
+
return ModelMessagesTypeAdapter.validate_python(raw_history)
|
|
311
|
+
|
|
312
|
+
def save_messages(self, chat_id: str, messages: List[Any]) -> None:
|
|
313
|
+
"""Save pydantic-ai messages to history.
|
|
314
|
+
|
|
315
|
+
This method converts pydantic-ai's ModelMessage objects to JSON
|
|
316
|
+
and stores them. Typically called with result.all_messages() after
|
|
317
|
+
an agent run.
|
|
318
|
+
|
|
319
|
+
Args:
|
|
320
|
+
chat_id: Unique identifier for the chat session.
|
|
321
|
+
messages: List of pydantic-ai ModelMessage objects
|
|
322
|
+
(e.g., from result.all_messages()).
|
|
323
|
+
|
|
324
|
+
Example:
|
|
325
|
+
>>> result = await agent.run("Hello", message_history=history.get_message_history("chat123"))
|
|
326
|
+
>>> history.save_messages("chat123", result.all_messages())
|
|
327
|
+
"""
|
|
328
|
+
if not PYDANTIC_AI_AVAILABLE:
|
|
329
|
+
raise ImportError(
|
|
330
|
+
"pydantic-ai is not installed. Install it with: pip install pydantic-ai"
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
self._ensure_initialized()
|
|
334
|
+
|
|
335
|
+
# Convert pydantic-ai messages to JSON-serializable format
|
|
336
|
+
json_messages = to_jsonable_python(messages)
|
|
337
|
+
self._chat_history[chat_id] = json_messages
|
|
338
|
+
|
|
339
|
+
if self.auto_save:
|
|
340
|
+
self._save()
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
if __name__ == "__main__":
|
|
344
|
+
# Example usage
|
|
345
|
+
history = JSONChatHistory(
|
|
346
|
+
config={"storage_path": "./test_chat_history.json"}
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
# Add messages
|
|
350
|
+
history.add_message("chat123", {
|
|
351
|
+
"role": "user",
|
|
352
|
+
"content": "Hello!",
|
|
353
|
+
"timestamp": "2025-03-18 10:00:00"
|
|
354
|
+
})
|
|
355
|
+
history.add_message("chat123", {
|
|
356
|
+
"role": "assistant",
|
|
357
|
+
"content": "Hi there! How can I help?",
|
|
358
|
+
"timestamp": "2025-03-18 10:00:05"
|
|
359
|
+
})
|
|
360
|
+
|
|
361
|
+
# Retrieve history
|
|
362
|
+
print("Chat history:", history.get_chat_history("chat123"))
|
|
363
|
+
print("All chat IDs:", history.get_all_chat_ids())
|
|
364
|
+
print("Readable format:", history.get_readable_chat_history("chat123"))
|
|
365
|
+
|
|
366
|
+
# Clean up
|
|
367
|
+
history.delete_chat_history("chat123")
|
|
368
|
+
print("After deletion:", history.get_all_chat_ids())
|
|
369
|
+
|
|
370
|
+
# Remove test file
|
|
371
|
+
import os
|
|
372
|
+
os.remove("./test_chat_history.json")
|