chuk-ai-session-manager 0.7.1__py3-none-any.whl → 0.8__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 +84 -40
- chuk_ai_session_manager/api/__init__.py +1 -1
- chuk_ai_session_manager/api/simple_api.py +53 -59
- chuk_ai_session_manager/exceptions.py +31 -17
- chuk_ai_session_manager/guards/__init__.py +118 -0
- chuk_ai_session_manager/guards/bindings.py +217 -0
- chuk_ai_session_manager/guards/cache.py +163 -0
- chuk_ai_session_manager/guards/manager.py +819 -0
- chuk_ai_session_manager/guards/models.py +498 -0
- chuk_ai_session_manager/guards/ungrounded.py +159 -0
- chuk_ai_session_manager/infinite_conversation.py +86 -79
- chuk_ai_session_manager/memory/__init__.py +247 -0
- chuk_ai_session_manager/memory/artifacts_bridge.py +469 -0
- chuk_ai_session_manager/memory/context_packer.py +347 -0
- chuk_ai_session_manager/memory/fault_handler.py +507 -0
- chuk_ai_session_manager/memory/manifest.py +307 -0
- chuk_ai_session_manager/memory/models.py +1084 -0
- chuk_ai_session_manager/memory/mutation_log.py +186 -0
- chuk_ai_session_manager/memory/pack_cache.py +206 -0
- chuk_ai_session_manager/memory/page_table.py +275 -0
- chuk_ai_session_manager/memory/prefetcher.py +192 -0
- chuk_ai_session_manager/memory/tlb.py +247 -0
- chuk_ai_session_manager/memory/vm_prompts.py +238 -0
- chuk_ai_session_manager/memory/working_set.py +574 -0
- chuk_ai_session_manager/models/__init__.py +21 -9
- chuk_ai_session_manager/models/event_source.py +3 -1
- chuk_ai_session_manager/models/event_type.py +10 -1
- chuk_ai_session_manager/models/session.py +103 -68
- chuk_ai_session_manager/models/session_event.py +69 -68
- chuk_ai_session_manager/models/session_metadata.py +9 -10
- chuk_ai_session_manager/models/session_run.py +21 -22
- chuk_ai_session_manager/models/token_usage.py +76 -76
- chuk_ai_session_manager/procedural_memory/__init__.py +70 -0
- chuk_ai_session_manager/procedural_memory/formatter.py +407 -0
- chuk_ai_session_manager/procedural_memory/manager.py +523 -0
- chuk_ai_session_manager/procedural_memory/models.py +371 -0
- chuk_ai_session_manager/sample_tools.py +79 -46
- chuk_ai_session_manager/session_aware_tool_processor.py +27 -16
- chuk_ai_session_manager/session_manager.py +238 -197
- chuk_ai_session_manager/session_prompt_builder.py +163 -111
- chuk_ai_session_manager/session_storage.py +45 -52
- {chuk_ai_session_manager-0.7.1.dist-info → chuk_ai_session_manager-0.8.dist-info}/METADATA +79 -3
- chuk_ai_session_manager-0.8.dist-info/RECORD +45 -0
- {chuk_ai_session_manager-0.7.1.dist-info → chuk_ai_session_manager-0.8.dist-info}/WHEEL +1 -1
- chuk_ai_session_manager-0.7.1.dist-info/RECORD +0 -22
- {chuk_ai_session_manager-0.7.1.dist-info → chuk_ai_session_manager-0.8.dist-info}/top_level.txt +0 -0
|
@@ -2,24 +2,27 @@
|
|
|
2
2
|
"""
|
|
3
3
|
Session model for the chuk session manager with improved async support.
|
|
4
4
|
"""
|
|
5
|
+
|
|
5
6
|
from __future__ import annotations
|
|
6
|
-
from datetime import datetime
|
|
7
|
+
from datetime import datetime
|
|
7
8
|
from typing import Any, Dict, List, Optional, Generic, TypeVar, Union
|
|
8
9
|
from uuid import uuid4
|
|
9
10
|
from pydantic import BaseModel, Field, model_validator
|
|
10
|
-
import asyncio
|
|
11
11
|
|
|
12
12
|
# Import models that Session depends on
|
|
13
13
|
from chuk_ai_session_manager.models.session_metadata import SessionMetadata
|
|
14
14
|
from chuk_ai_session_manager.models.session_event import SessionEvent
|
|
15
15
|
from chuk_ai_session_manager.models.token_usage import TokenUsage, TokenSummary
|
|
16
|
+
|
|
16
17
|
# Import SessionRun and RunStatus directly to avoid circular import
|
|
17
18
|
from chuk_ai_session_manager.models.session_run import SessionRun, RunStatus
|
|
18
19
|
|
|
19
|
-
MessageT = TypeVar(
|
|
20
|
+
MessageT = TypeVar("MessageT")
|
|
21
|
+
|
|
20
22
|
|
|
21
23
|
class Session(BaseModel, Generic[MessageT]):
|
|
22
24
|
"""A standalone conversation session with hierarchical support and async methods."""
|
|
25
|
+
|
|
23
26
|
id: str = Field(default_factory=lambda: str(uuid4()))
|
|
24
27
|
metadata: SessionMetadata = Field(default_factory=SessionMetadata)
|
|
25
28
|
|
|
@@ -30,31 +33,35 @@ class Session(BaseModel, Generic[MessageT]):
|
|
|
30
33
|
runs: List[SessionRun] = Field(default_factory=list)
|
|
31
34
|
events: List[SessionEvent[MessageT]] = Field(default_factory=list)
|
|
32
35
|
state: Dict[str, Any] = Field(default_factory=dict)
|
|
33
|
-
|
|
36
|
+
|
|
34
37
|
# Token tracking
|
|
35
38
|
token_summary: TokenSummary = Field(default_factory=TokenSummary)
|
|
36
39
|
|
|
37
40
|
@model_validator(mode="after")
|
|
38
41
|
def _sync_hierarchy(cls, model: Session) -> Session:
|
|
39
42
|
"""After creation, sync this session with its parent in the store.
|
|
40
|
-
|
|
41
|
-
Note: This is synchronous for compatibility with Pydantic.
|
|
43
|
+
|
|
44
|
+
Note: This is synchronous for compatibility with Pydantic.
|
|
42
45
|
For async parent syncing, use async_init() after creation.
|
|
43
46
|
"""
|
|
44
47
|
# This validator will be called during model creation,
|
|
45
48
|
# but won't actually sync with storage - that requires async
|
|
46
49
|
return model
|
|
47
|
-
|
|
50
|
+
|
|
48
51
|
async def async_init(self) -> None:
|
|
49
52
|
"""
|
|
50
53
|
Initialize async components of the session.
|
|
51
|
-
|
|
54
|
+
|
|
52
55
|
Call this after creating a new session to properly set up
|
|
53
56
|
parent-child relationships in the async storage.
|
|
54
57
|
"""
|
|
55
58
|
if self.parent_id:
|
|
56
59
|
# Import here to avoid circular import
|
|
57
|
-
from chuk_ai_session_manager.session_storage import
|
|
60
|
+
from chuk_ai_session_manager.session_storage import (
|
|
61
|
+
get_backend,
|
|
62
|
+
ChukSessionsStore,
|
|
63
|
+
)
|
|
64
|
+
|
|
58
65
|
backend = get_backend()
|
|
59
66
|
store = ChukSessionsStore(backend)
|
|
60
67
|
parent = await store.get(self.parent_id)
|
|
@@ -76,12 +83,12 @@ class Session(BaseModel, Generic[MessageT]):
|
|
|
76
83
|
if run.status == RunStatus.RUNNING:
|
|
77
84
|
return run
|
|
78
85
|
return None
|
|
79
|
-
|
|
86
|
+
|
|
80
87
|
@property
|
|
81
88
|
def total_tokens(self) -> int:
|
|
82
89
|
"""Return the total number of tokens used in this session."""
|
|
83
90
|
return self.token_summary.total_tokens
|
|
84
|
-
|
|
91
|
+
|
|
85
92
|
@property
|
|
86
93
|
def total_cost(self) -> float:
|
|
87
94
|
"""Return the total estimated cost of this session."""
|
|
@@ -92,7 +99,11 @@ class Session(BaseModel, Generic[MessageT]):
|
|
|
92
99
|
if child_id not in self.child_ids:
|
|
93
100
|
self.child_ids.append(child_id)
|
|
94
101
|
# Save the updated session
|
|
95
|
-
from chuk_ai_session_manager.session_storage import
|
|
102
|
+
from chuk_ai_session_manager.session_storage import (
|
|
103
|
+
get_backend,
|
|
104
|
+
ChukSessionsStore,
|
|
105
|
+
)
|
|
106
|
+
|
|
96
107
|
backend = get_backend()
|
|
97
108
|
store = ChukSessionsStore(backend)
|
|
98
109
|
await store.save(self)
|
|
@@ -102,7 +113,11 @@ class Session(BaseModel, Generic[MessageT]):
|
|
|
102
113
|
if child_id in self.child_ids:
|
|
103
114
|
self.child_ids.remove(child_id)
|
|
104
115
|
# Save the updated session
|
|
105
|
-
from chuk_ai_session_manager.session_storage import
|
|
116
|
+
from chuk_ai_session_manager.session_storage import (
|
|
117
|
+
get_backend,
|
|
118
|
+
ChukSessionsStore,
|
|
119
|
+
)
|
|
120
|
+
|
|
106
121
|
backend = get_backend()
|
|
107
122
|
store = ChukSessionsStore(backend)
|
|
108
123
|
await store.save(self)
|
|
@@ -111,12 +126,16 @@ class Session(BaseModel, Generic[MessageT]):
|
|
|
111
126
|
"""Fetch ancestor sessions from store asynchronously."""
|
|
112
127
|
result: List[Session] = []
|
|
113
128
|
current = self.parent_id
|
|
114
|
-
|
|
129
|
+
|
|
115
130
|
# Import here to avoid circular import
|
|
116
|
-
from chuk_ai_session_manager.session_storage import
|
|
131
|
+
from chuk_ai_session_manager.session_storage import (
|
|
132
|
+
get_backend,
|
|
133
|
+
ChukSessionsStore,
|
|
134
|
+
)
|
|
135
|
+
|
|
117
136
|
backend = get_backend()
|
|
118
137
|
store = ChukSessionsStore(backend)
|
|
119
|
-
|
|
138
|
+
|
|
120
139
|
while current:
|
|
121
140
|
parent = await store.get(current)
|
|
122
141
|
if not parent:
|
|
@@ -129,12 +148,16 @@ class Session(BaseModel, Generic[MessageT]):
|
|
|
129
148
|
"""Fetch all descendant sessions from store in DFS order asynchronously."""
|
|
130
149
|
result: List[Session] = []
|
|
131
150
|
stack = list(self.child_ids)
|
|
132
|
-
|
|
151
|
+
|
|
133
152
|
# Import here to avoid circular import
|
|
134
|
-
from chuk_ai_session_manager.session_storage import
|
|
153
|
+
from chuk_ai_session_manager.session_storage import (
|
|
154
|
+
get_backend,
|
|
155
|
+
ChukSessionsStore,
|
|
156
|
+
)
|
|
157
|
+
|
|
135
158
|
backend = get_backend()
|
|
136
159
|
store = ChukSessionsStore(backend)
|
|
137
|
-
|
|
160
|
+
|
|
138
161
|
while stack:
|
|
139
162
|
cid = stack.pop()
|
|
140
163
|
child = await store.get(cid)
|
|
@@ -142,160 +165,166 @@ class Session(BaseModel, Generic[MessageT]):
|
|
|
142
165
|
result.append(child)
|
|
143
166
|
stack.extend(child.child_ids)
|
|
144
167
|
return result
|
|
145
|
-
|
|
168
|
+
|
|
146
169
|
async def add_event(self, event: SessionEvent[MessageT]) -> None:
|
|
147
170
|
"""
|
|
148
171
|
Add an event to the session and update token tracking asynchronously.
|
|
149
|
-
|
|
172
|
+
|
|
150
173
|
Args:
|
|
151
174
|
event: The event to add
|
|
152
175
|
"""
|
|
153
176
|
# Add the event
|
|
154
177
|
self.events.append(event)
|
|
155
|
-
|
|
178
|
+
|
|
156
179
|
# Update token summary if this event has token usage
|
|
157
180
|
if event.token_usage:
|
|
158
181
|
await self.token_summary.add_usage(event.token_usage)
|
|
159
|
-
|
|
182
|
+
|
|
160
183
|
async def add_event_and_save(self, event: SessionEvent[MessageT]) -> None:
|
|
161
184
|
"""
|
|
162
185
|
Add an event to the session, update token tracking, and save the session.
|
|
163
|
-
|
|
186
|
+
|
|
164
187
|
Args:
|
|
165
188
|
event: The event to add
|
|
166
189
|
"""
|
|
167
190
|
# Add the event asynchronously
|
|
168
191
|
await self.add_event(event)
|
|
169
|
-
|
|
192
|
+
|
|
170
193
|
# Save the session
|
|
171
|
-
from chuk_ai_session_manager.session_storage import
|
|
194
|
+
from chuk_ai_session_manager.session_storage import (
|
|
195
|
+
get_backend,
|
|
196
|
+
ChukSessionsStore,
|
|
197
|
+
)
|
|
198
|
+
|
|
172
199
|
backend = get_backend()
|
|
173
200
|
store = ChukSessionsStore(backend)
|
|
174
201
|
await store.save(self)
|
|
175
|
-
|
|
202
|
+
|
|
176
203
|
async def get_token_usage_by_source(self) -> Dict[str, TokenSummary]:
|
|
177
204
|
"""
|
|
178
205
|
Get token usage statistics grouped by event source asynchronously.
|
|
179
|
-
|
|
206
|
+
|
|
180
207
|
Returns:
|
|
181
208
|
A dictionary mapping event sources to token summaries
|
|
182
209
|
"""
|
|
183
210
|
result: Dict[str, TokenSummary] = {}
|
|
184
|
-
|
|
211
|
+
|
|
185
212
|
for event in self.events:
|
|
186
213
|
if not event.token_usage:
|
|
187
214
|
continue
|
|
188
|
-
|
|
215
|
+
|
|
189
216
|
# Use the string value of the enum for the key
|
|
190
|
-
source =
|
|
217
|
+
source = (
|
|
218
|
+
event.source.value
|
|
219
|
+
if hasattr(event.source, "value")
|
|
220
|
+
else str(event.source)
|
|
221
|
+
)
|
|
191
222
|
if source not in result:
|
|
192
223
|
result[source] = TokenSummary()
|
|
193
|
-
|
|
224
|
+
|
|
194
225
|
await result[source].add_usage(event.token_usage)
|
|
195
|
-
|
|
226
|
+
|
|
196
227
|
return result
|
|
197
|
-
|
|
228
|
+
|
|
198
229
|
async def get_token_usage_by_run(self) -> Dict[str, TokenSummary]:
|
|
199
230
|
"""
|
|
200
231
|
Get token usage statistics grouped by run asynchronously.
|
|
201
|
-
|
|
232
|
+
|
|
202
233
|
Returns:
|
|
203
234
|
A dictionary mapping run IDs to token summaries
|
|
204
235
|
"""
|
|
205
236
|
result: Dict[str, TokenSummary] = {}
|
|
206
|
-
|
|
237
|
+
|
|
207
238
|
# Add an entry for events without a run
|
|
208
239
|
result["no_run"] = TokenSummary()
|
|
209
|
-
|
|
240
|
+
|
|
210
241
|
for event in self.events:
|
|
211
242
|
if not event.token_usage:
|
|
212
243
|
continue
|
|
213
|
-
|
|
244
|
+
|
|
214
245
|
run_id = event.task_id or "no_run"
|
|
215
246
|
if run_id not in result:
|
|
216
247
|
result[run_id] = TokenSummary()
|
|
217
|
-
|
|
248
|
+
|
|
218
249
|
await result[run_id].add_usage(event.token_usage)
|
|
219
|
-
|
|
250
|
+
|
|
220
251
|
return result
|
|
221
|
-
|
|
252
|
+
|
|
222
253
|
async def count_message_tokens(
|
|
223
|
-
self,
|
|
224
|
-
message: Union[str, Dict[str, Any]],
|
|
225
|
-
model: str = "gpt-3.5-turbo"
|
|
254
|
+
self, message: Union[str, Dict[str, Any]], model: str = "gpt-3.5-turbo"
|
|
226
255
|
) -> int:
|
|
227
256
|
"""
|
|
228
257
|
Count tokens in a message asynchronously.
|
|
229
|
-
|
|
258
|
+
|
|
230
259
|
Args:
|
|
231
260
|
message: The message to count tokens for (string or dict)
|
|
232
261
|
model: The model to use for counting
|
|
233
|
-
|
|
262
|
+
|
|
234
263
|
Returns:
|
|
235
264
|
The number of tokens in the message
|
|
236
265
|
"""
|
|
237
266
|
# If message is already a string, count directly
|
|
238
267
|
if isinstance(message, str):
|
|
239
268
|
return await TokenUsage.count_tokens(message, model)
|
|
240
|
-
|
|
269
|
+
|
|
241
270
|
# If it's a dict (like OpenAI messages), extract content
|
|
242
271
|
if isinstance(message, dict) and "content" in message:
|
|
243
272
|
return await TokenUsage.count_tokens(message["content"], model)
|
|
244
|
-
|
|
273
|
+
|
|
245
274
|
# If it's some other object, convert to string and count
|
|
246
275
|
return await TokenUsage.count_tokens(str(message), model)
|
|
247
|
-
|
|
276
|
+
|
|
248
277
|
async def set_state(self, key: str, value: Any) -> None:
|
|
249
278
|
"""
|
|
250
279
|
Set a state value asynchronously.
|
|
251
|
-
|
|
280
|
+
|
|
252
281
|
Args:
|
|
253
282
|
key: The state key to set
|
|
254
283
|
value: The value to set
|
|
255
284
|
"""
|
|
256
285
|
self.state[key] = value
|
|
257
|
-
|
|
286
|
+
|
|
258
287
|
# Auto-save if needed (could be added as an option)
|
|
259
288
|
# from chuk_ai_session_manager.chuk_sessions_storage import get_backend, ChukSessionsStore
|
|
260
289
|
# backend = get_backend()
|
|
261
290
|
# store = ChukSessionsStore(backend)
|
|
262
291
|
# await store.save(self)
|
|
263
|
-
|
|
292
|
+
|
|
264
293
|
async def get_state(self, key: str, default: Any = None) -> Any:
|
|
265
294
|
"""
|
|
266
295
|
Get a state value asynchronously.
|
|
267
|
-
|
|
296
|
+
|
|
268
297
|
Args:
|
|
269
298
|
key: The state key to retrieve
|
|
270
299
|
default: Default value to return if key not found
|
|
271
|
-
|
|
300
|
+
|
|
272
301
|
Returns:
|
|
273
302
|
The state value or default if not found
|
|
274
303
|
"""
|
|
275
304
|
return self.state.get(key, default)
|
|
276
|
-
|
|
305
|
+
|
|
277
306
|
async def has_state(self, key: str) -> bool:
|
|
278
307
|
"""
|
|
279
308
|
Check if a state key exists asynchronously.
|
|
280
|
-
|
|
309
|
+
|
|
281
310
|
Args:
|
|
282
311
|
key: The state key to check
|
|
283
|
-
|
|
312
|
+
|
|
284
313
|
Returns:
|
|
285
314
|
True if the key exists in state
|
|
286
315
|
"""
|
|
287
316
|
return key in self.state
|
|
288
|
-
|
|
317
|
+
|
|
289
318
|
async def remove_state(self, key: str) -> None:
|
|
290
319
|
"""
|
|
291
320
|
Remove a state key-value pair asynchronously.
|
|
292
|
-
|
|
321
|
+
|
|
293
322
|
Args:
|
|
294
323
|
key: The state key to remove
|
|
295
324
|
"""
|
|
296
325
|
if key in self.state:
|
|
297
326
|
del self.state[key]
|
|
298
|
-
|
|
327
|
+
|
|
299
328
|
# Auto-save if needed (could be added as an option)
|
|
300
329
|
# from chuk_ai_session_manager.chuk_sessions_storage import get_backend, ChukSessionsStore
|
|
301
330
|
# backend = get_backend()
|
|
@@ -303,15 +332,17 @@ class Session(BaseModel, Generic[MessageT]):
|
|
|
303
332
|
# await store.save(self)
|
|
304
333
|
|
|
305
334
|
@classmethod
|
|
306
|
-
async def create(
|
|
335
|
+
async def create(
|
|
336
|
+
cls, session_id: Optional[str] = None, parent_id: Optional[str] = None, **kwargs
|
|
337
|
+
) -> Session:
|
|
307
338
|
"""
|
|
308
339
|
Create a new session asynchronously, handling parent-child relationships.
|
|
309
|
-
|
|
340
|
+
|
|
310
341
|
Args:
|
|
311
342
|
session_id: Optional session ID to use (if not provided, generates a new one)
|
|
312
343
|
parent_id: Optional parent session ID
|
|
313
344
|
**kwargs: Additional arguments for Session initialization
|
|
314
|
-
|
|
345
|
+
|
|
315
346
|
Returns:
|
|
316
347
|
A new Session instance with parent-child relationships set up
|
|
317
348
|
"""
|
|
@@ -320,13 +351,17 @@ class Session(BaseModel, Generic[MessageT]):
|
|
|
320
351
|
session = cls(id=session_id, parent_id=parent_id, **kwargs)
|
|
321
352
|
else:
|
|
322
353
|
session = cls(parent_id=parent_id, **kwargs)
|
|
323
|
-
|
|
354
|
+
|
|
324
355
|
await session.async_init()
|
|
325
|
-
|
|
356
|
+
|
|
326
357
|
# Save the new session
|
|
327
|
-
from chuk_ai_session_manager.session_storage import
|
|
358
|
+
from chuk_ai_session_manager.session_storage import (
|
|
359
|
+
get_backend,
|
|
360
|
+
ChukSessionsStore,
|
|
361
|
+
)
|
|
362
|
+
|
|
328
363
|
backend = get_backend()
|
|
329
364
|
store = ChukSessionsStore(backend)
|
|
330
365
|
await store.save(session)
|
|
331
|
-
|
|
332
|
-
return session
|
|
366
|
+
|
|
367
|
+
return session
|