chuk-ai-session-manager 0.4__py3-none-any.whl → 0.4.1__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.
- chuk_ai_session_manager/__init__.py +307 -51
- chuk_ai_session_manager/api/simple_api.py +276 -425
- chuk_ai_session_manager/models/session.py +11 -4
- chuk_ai_session_manager/models/session_event.py +185 -81
- chuk_ai_session_manager/session_manager.py +760 -0
- chuk_ai_session_manager/session_storage.py +19 -6
- {chuk_ai_session_manager-0.4.dist-info → chuk_ai_session_manager-0.4.1.dist-info}/METADATA +2 -1
- {chuk_ai_session_manager-0.4.dist-info → chuk_ai_session_manager-0.4.1.dist-info}/RECORD +10 -9
- {chuk_ai_session_manager-0.4.dist-info → chuk_ai_session_manager-0.4.1.dist-info}/WHEEL +0 -0
- {chuk_ai_session_manager-0.4.dist-info → chuk_ai_session_manager-0.4.1.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# chuk_ai_session_manager/models/session.py
|
|
1
|
+
# src/chuk_ai_session_manager/models/session.py
|
|
2
2
|
"""
|
|
3
3
|
Session model for the chuk session manager with improved async support.
|
|
4
4
|
"""
|
|
@@ -186,7 +186,8 @@ class Session(BaseModel, Generic[MessageT]):
|
|
|
186
186
|
if not event.token_usage:
|
|
187
187
|
continue
|
|
188
188
|
|
|
189
|
-
|
|
189
|
+
# Use the string value of the enum for the key
|
|
190
|
+
source = event.source.value if hasattr(event.source, 'value') else str(event.source)
|
|
190
191
|
if source not in result:
|
|
191
192
|
result[source] = TokenSummary()
|
|
192
193
|
|
|
@@ -302,18 +303,24 @@ class Session(BaseModel, Generic[MessageT]):
|
|
|
302
303
|
# await store.save(self)
|
|
303
304
|
|
|
304
305
|
@classmethod
|
|
305
|
-
async def create(cls, parent_id: Optional[str] = None, **kwargs) -> Session:
|
|
306
|
+
async def create(cls, session_id: Optional[str] = None, parent_id: Optional[str] = None, **kwargs) -> Session:
|
|
306
307
|
"""
|
|
307
308
|
Create a new session asynchronously, handling parent-child relationships.
|
|
308
309
|
|
|
309
310
|
Args:
|
|
311
|
+
session_id: Optional session ID to use (if not provided, generates a new one)
|
|
310
312
|
parent_id: Optional parent session ID
|
|
311
313
|
**kwargs: Additional arguments for Session initialization
|
|
312
314
|
|
|
313
315
|
Returns:
|
|
314
316
|
A new Session instance with parent-child relationships set up
|
|
315
317
|
"""
|
|
316
|
-
|
|
318
|
+
# Allow passing a specific session ID
|
|
319
|
+
if session_id:
|
|
320
|
+
session = cls(id=session_id, parent_id=parent_id, **kwargs)
|
|
321
|
+
else:
|
|
322
|
+
session = cls(parent_id=parent_id, **kwargs)
|
|
323
|
+
|
|
317
324
|
await session.async_init()
|
|
318
325
|
|
|
319
326
|
# Save the new session
|
|
@@ -1,34 +1,41 @@
|
|
|
1
|
-
# chuk_ai_session_manager/models/session_event.py
|
|
1
|
+
# src/chuk_ai_session_manager/models/session_event.py
|
|
2
2
|
"""
|
|
3
3
|
Session event model for the chuk session manager with improved async support.
|
|
4
4
|
"""
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
from datetime import datetime, timezone
|
|
7
|
-
from typing import Any, Dict, Generic, Optional, TypeVar
|
|
7
|
+
from typing import Any, Dict, Generic, Optional, TypeVar
|
|
8
8
|
from uuid import uuid4
|
|
9
9
|
from pydantic import BaseModel, Field, ConfigDict
|
|
10
10
|
|
|
11
|
-
# session manager
|
|
12
11
|
from chuk_ai_session_manager.models.event_source import EventSource
|
|
13
12
|
from chuk_ai_session_manager.models.event_type import EventType
|
|
14
13
|
from chuk_ai_session_manager.models.token_usage import TokenUsage
|
|
15
14
|
|
|
16
|
-
# Generic type for event message content
|
|
17
15
|
MessageT = TypeVar('MessageT')
|
|
18
16
|
|
|
19
17
|
class SessionEvent(BaseModel, Generic[MessageT]):
|
|
20
|
-
"""
|
|
18
|
+
"""
|
|
19
|
+
A single event within a session.
|
|
20
|
+
|
|
21
|
+
Events track all interactions in a session including messages,
|
|
22
|
+
tool calls, summaries, and other activities.
|
|
23
|
+
"""
|
|
21
24
|
model_config = ConfigDict(arbitrary_types_allowed=True)
|
|
22
|
-
|
|
25
|
+
|
|
23
26
|
id: str = Field(default_factory=lambda: str(uuid4()))
|
|
27
|
+
message: MessageT
|
|
24
28
|
timestamp: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
|
|
30
|
+
# Make source and type have defaults for backward compatibility with tests
|
|
31
|
+
source: EventSource = Field(default=EventSource.SYSTEM)
|
|
32
|
+
type: EventType = Field(default=EventType.MESSAGE)
|
|
33
|
+
|
|
29
34
|
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
35
|
+
parent_event_id: Optional[str] = None
|
|
36
|
+
task_id: Optional[str] = None
|
|
30
37
|
|
|
31
|
-
#
|
|
38
|
+
# Token tracking
|
|
32
39
|
token_usage: Optional[TokenUsage] = None
|
|
33
40
|
|
|
34
41
|
@classmethod
|
|
@@ -38,116 +45,126 @@ class SessionEvent(BaseModel, Generic[MessageT]):
|
|
|
38
45
|
prompt: str,
|
|
39
46
|
completion: Optional[str] = None,
|
|
40
47
|
model: str = "gpt-3.5-turbo",
|
|
41
|
-
source: EventSource = EventSource.
|
|
48
|
+
source: EventSource = EventSource.SYSTEM,
|
|
42
49
|
type: EventType = EventType.MESSAGE,
|
|
43
|
-
|
|
44
|
-
metadata: Optional[Dict[str, Any]] = None
|
|
50
|
+
**kwargs
|
|
45
51
|
) -> SessionEvent[MessageT]:
|
|
46
52
|
"""
|
|
47
|
-
Create a
|
|
53
|
+
Create a new SessionEvent with automatic token counting.
|
|
48
54
|
|
|
49
55
|
Args:
|
|
50
56
|
message: The message content
|
|
51
|
-
prompt: The prompt text
|
|
52
|
-
completion:
|
|
53
|
-
model: The model
|
|
54
|
-
source: The source
|
|
55
|
-
type: The type
|
|
56
|
-
|
|
57
|
-
metadata: Optional additional metadata
|
|
57
|
+
prompt: The prompt text for token counting
|
|
58
|
+
completion: Optional completion text for token counting
|
|
59
|
+
model: The model to use for token counting
|
|
60
|
+
source: The event source
|
|
61
|
+
type: The event type
|
|
62
|
+
**kwargs: Additional fields for the event
|
|
58
63
|
|
|
59
64
|
Returns:
|
|
60
|
-
A new SessionEvent with token usage
|
|
65
|
+
A new SessionEvent instance with token usage calculated
|
|
61
66
|
"""
|
|
62
|
-
#
|
|
63
|
-
token_usage = await TokenUsage.from_text(
|
|
67
|
+
# Create token usage
|
|
68
|
+
token_usage = await TokenUsage.from_text(
|
|
69
|
+
prompt=prompt,
|
|
70
|
+
completion=completion,
|
|
71
|
+
model=model
|
|
72
|
+
)
|
|
64
73
|
|
|
65
74
|
# Create the event
|
|
66
75
|
event = cls(
|
|
67
76
|
message=message,
|
|
68
|
-
task_id=task_id,
|
|
69
|
-
type=type,
|
|
70
77
|
source=source,
|
|
71
|
-
|
|
72
|
-
token_usage=token_usage
|
|
78
|
+
type=type,
|
|
79
|
+
token_usage=token_usage,
|
|
80
|
+
**kwargs
|
|
73
81
|
)
|
|
74
82
|
|
|
75
83
|
return event
|
|
76
84
|
|
|
77
85
|
async def update_token_usage(
|
|
78
86
|
self,
|
|
79
|
-
prompt: Optional[str] = None,
|
|
87
|
+
prompt: Optional[str] = None,
|
|
80
88
|
completion: Optional[str] = None,
|
|
81
|
-
|
|
89
|
+
prompt_tokens: Optional[int] = None,
|
|
90
|
+
completion_tokens: Optional[int] = None,
|
|
91
|
+
model: str = "gpt-3.5-turbo"
|
|
82
92
|
) -> None:
|
|
83
93
|
"""
|
|
84
|
-
Update token usage
|
|
94
|
+
Update the token usage for this event.
|
|
95
|
+
|
|
96
|
+
This method supports two modes:
|
|
97
|
+
1. Pass prompt/completion strings to calculate tokens
|
|
98
|
+
2. Pass prompt_tokens/completion_tokens directly
|
|
85
99
|
|
|
86
100
|
Args:
|
|
87
|
-
prompt:
|
|
88
|
-
completion:
|
|
89
|
-
|
|
101
|
+
prompt: Optional prompt text to calculate tokens from
|
|
102
|
+
completion: Optional completion text to calculate tokens from
|
|
103
|
+
prompt_tokens: Optional number of prompt tokens (if already calculated)
|
|
104
|
+
completion_tokens: Optional number of completion tokens (if already calculated)
|
|
105
|
+
model: The model used for token calculation
|
|
90
106
|
"""
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
self.token_usage = TokenUsage(
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
prompt_tokens = await TokenUsage.count_tokens(prompt, self.token_usage.model)
|
|
103
|
-
self.token_usage.prompt_tokens = prompt_tokens
|
|
107
|
+
if prompt is not None or completion is not None:
|
|
108
|
+
# Calculate tokens from text
|
|
109
|
+
self.token_usage = await TokenUsage.from_text(
|
|
110
|
+
prompt=prompt or "",
|
|
111
|
+
completion=completion,
|
|
112
|
+
model=model
|
|
113
|
+
)
|
|
114
|
+
elif prompt_tokens is not None or completion_tokens is not None:
|
|
115
|
+
# Use provided token counts
|
|
116
|
+
if not self.token_usage:
|
|
117
|
+
self.token_usage = TokenUsage(model=model)
|
|
104
118
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
completion_tokens
|
|
108
|
-
|
|
119
|
+
if prompt_tokens is not None:
|
|
120
|
+
self.token_usage.prompt_tokens = prompt_tokens
|
|
121
|
+
if completion_tokens is not None:
|
|
122
|
+
self.token_usage.completion_tokens = completion_tokens
|
|
109
123
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if self.token_usage.model:
|
|
113
|
-
# Use async method for cost calculation
|
|
114
|
-
self.token_usage.estimated_cost_usd = await self.token_usage.calculate_cost()
|
|
124
|
+
# Update total
|
|
125
|
+
self.token_usage.total_tokens = self.token_usage.prompt_tokens + self.token_usage.completion_tokens
|
|
115
126
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
Args:
|
|
121
|
-
key: The metadata key to retrieve
|
|
122
|
-
default: Default value to return if key not found
|
|
123
|
-
|
|
124
|
-
Returns:
|
|
125
|
-
The metadata value or default if not found
|
|
126
|
-
"""
|
|
127
|
-
return self.metadata.get(key, default)
|
|
128
|
-
|
|
127
|
+
# Recalculate cost
|
|
128
|
+
self.token_usage.estimated_cost_usd = self.token_usage._calculate_cost_sync()
|
|
129
|
+
|
|
129
130
|
async def set_metadata(self, key: str, value: Any) -> None:
|
|
130
|
-
"""
|
|
131
|
+
"""
|
|
132
|
+
Set a metadata value asynchronously.
|
|
131
133
|
|
|
132
134
|
Args:
|
|
133
|
-
key: The metadata key
|
|
135
|
+
key: The metadata key
|
|
134
136
|
value: The value to set
|
|
135
137
|
"""
|
|
136
138
|
self.metadata[key] = value
|
|
139
|
+
|
|
140
|
+
async def get_metadata(self, key: str, default: Any = None) -> Any:
|
|
141
|
+
"""
|
|
142
|
+
Get a metadata value asynchronously.
|
|
137
143
|
|
|
144
|
+
Args:
|
|
145
|
+
key: The metadata key
|
|
146
|
+
default: Default value if key not found
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
The metadata value or default
|
|
150
|
+
"""
|
|
151
|
+
return self.metadata.get(key, default)
|
|
152
|
+
|
|
138
153
|
async def has_metadata(self, key: str) -> bool:
|
|
139
|
-
"""
|
|
154
|
+
"""
|
|
155
|
+
Check if a metadata key exists asynchronously.
|
|
140
156
|
|
|
141
157
|
Args:
|
|
142
158
|
key: The metadata key to check
|
|
143
159
|
|
|
144
160
|
Returns:
|
|
145
|
-
True if the key exists
|
|
161
|
+
True if the key exists
|
|
146
162
|
"""
|
|
147
163
|
return key in self.metadata
|
|
148
|
-
|
|
164
|
+
|
|
149
165
|
async def remove_metadata(self, key: str) -> None:
|
|
150
|
-
"""
|
|
166
|
+
"""
|
|
167
|
+
Remove a metadata key-value pair asynchronously.
|
|
151
168
|
|
|
152
169
|
Args:
|
|
153
170
|
key: The metadata key to remove
|
|
@@ -155,12 +172,99 @@ class SessionEvent(BaseModel, Generic[MessageT]):
|
|
|
155
172
|
if key in self.metadata:
|
|
156
173
|
del self.metadata[key]
|
|
157
174
|
|
|
158
|
-
# Alternative async method for updating metadata for backward compatibility
|
|
159
175
|
async def update_metadata(self, key: str, value: Any) -> None:
|
|
160
|
-
"""
|
|
176
|
+
"""
|
|
177
|
+
Update or add a metadata value asynchronously.
|
|
161
178
|
|
|
162
179
|
Args:
|
|
163
|
-
key: The metadata key
|
|
164
|
-
value: The value
|
|
180
|
+
key: The metadata key
|
|
181
|
+
value: The new value
|
|
165
182
|
"""
|
|
166
|
-
|
|
183
|
+
self.metadata[key] = value
|
|
184
|
+
|
|
185
|
+
async def merge_metadata(self, new_metadata: Dict[str, Any]) -> None:
|
|
186
|
+
"""
|
|
187
|
+
Merge new metadata with existing metadata asynchronously.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
new_metadata: Dictionary of metadata to merge
|
|
191
|
+
"""
|
|
192
|
+
self.metadata.update(new_metadata)
|
|
193
|
+
|
|
194
|
+
async def clear_metadata(self) -> None:
|
|
195
|
+
"""Clear all metadata asynchronously."""
|
|
196
|
+
self.metadata.clear()
|
|
197
|
+
|
|
198
|
+
async def calculate_tokens(self, model: str = "gpt-3.5-turbo") -> int:
|
|
199
|
+
"""
|
|
200
|
+
Calculate tokens for this event's message asynchronously.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
model: The model to use for token counting
|
|
204
|
+
|
|
205
|
+
Returns:
|
|
206
|
+
The number of tokens in the message
|
|
207
|
+
"""
|
|
208
|
+
if self.token_usage:
|
|
209
|
+
return self.token_usage.total_tokens
|
|
210
|
+
|
|
211
|
+
# Calculate tokens from message
|
|
212
|
+
message_str = str(self.message) if not isinstance(self.message, str) else self.message
|
|
213
|
+
return await TokenUsage.count_tokens(message_str, model)
|
|
214
|
+
|
|
215
|
+
def is_child_of(self, parent_event_id: str) -> bool:
|
|
216
|
+
"""
|
|
217
|
+
Check if this event is a child of another event.
|
|
218
|
+
|
|
219
|
+
Args:
|
|
220
|
+
parent_event_id: The ID of the potential parent event
|
|
221
|
+
|
|
222
|
+
Returns:
|
|
223
|
+
True if this event is a child of the specified event
|
|
224
|
+
"""
|
|
225
|
+
return self.parent_event_id == parent_event_id
|
|
226
|
+
|
|
227
|
+
def is_part_of_task(self, task_id: str) -> bool:
|
|
228
|
+
"""
|
|
229
|
+
Check if this event is part of a specific task.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
task_id: The task ID to check
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
True if this event is part of the specified task
|
|
236
|
+
"""
|
|
237
|
+
return self.task_id == task_id
|
|
238
|
+
|
|
239
|
+
async def to_dict(self) -> Dict[str, Any]:
|
|
240
|
+
"""
|
|
241
|
+
Convert the event to a dictionary asynchronously.
|
|
242
|
+
|
|
243
|
+
Returns:
|
|
244
|
+
Dictionary representation of the event
|
|
245
|
+
"""
|
|
246
|
+
result = {
|
|
247
|
+
"id": self.id,
|
|
248
|
+
"message": self.message,
|
|
249
|
+
"timestamp": self.timestamp.isoformat(),
|
|
250
|
+
"source": self.source.value,
|
|
251
|
+
"type": self.type.value,
|
|
252
|
+
"metadata": self.metadata
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if self.parent_event_id:
|
|
256
|
+
result["parent_event_id"] = self.parent_event_id
|
|
257
|
+
|
|
258
|
+
if self.task_id:
|
|
259
|
+
result["task_id"] = self.task_id
|
|
260
|
+
|
|
261
|
+
if self.token_usage:
|
|
262
|
+
result["token_usage"] = {
|
|
263
|
+
"prompt_tokens": self.token_usage.prompt_tokens,
|
|
264
|
+
"completion_tokens": self.token_usage.completion_tokens,
|
|
265
|
+
"total_tokens": self.token_usage.total_tokens,
|
|
266
|
+
"model": self.token_usage.model,
|
|
267
|
+
"estimated_cost_usd": self.token_usage.estimated_cost_usd
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
return result
|