chuk-ai-session-manager 0.3__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 -49
- 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/models/token_usage.py +13 -2
- chuk_ai_session_manager/sample_tools.py +1 -1
- chuk_ai_session_manager/session_manager.py +760 -0
- chuk_ai_session_manager/session_prompt_builder.py +70 -62
- chuk_ai_session_manager/session_storage.py +19 -6
- {chuk_ai_session_manager-0.3.dist-info → chuk_ai_session_manager-0.4.1.dist-info}/METADATA +2 -38
- chuk_ai_session_manager-0.4.1.dist-info/RECORD +22 -0
- chuk_ai_session_manager/utils/__init__.py +0 -0
- chuk_ai_session_manager/utils/status_display_utils.py +0 -474
- chuk_ai_session_manager-0.3.dist-info/RECORD +0 -23
- {chuk_ai_session_manager-0.3.dist-info → chuk_ai_session_manager-0.4.1.dist-info}/WHEEL +0 -0
- {chuk_ai_session_manager-0.3.dist-info → chuk_ai_session_manager-0.4.1.dist-info}/top_level.txt +0 -0
|
@@ -37,7 +37,8 @@ async def build_prompt_from_session(
|
|
|
37
37
|
max_tokens: Optional[int] = None,
|
|
38
38
|
model: str = "gpt-3.5-turbo",
|
|
39
39
|
include_parent_context: bool = False,
|
|
40
|
-
current_query: Optional[str] = None
|
|
40
|
+
current_query: Optional[str] = None,
|
|
41
|
+
max_history: int = 5 # Add this parameter for conversation strategy
|
|
41
42
|
) -> List[Dict[str, str]]:
|
|
42
43
|
"""
|
|
43
44
|
Build a prompt for the next LLM call from a Session asynchronously.
|
|
@@ -49,6 +50,7 @@ async def build_prompt_from_session(
|
|
|
49
50
|
model: Model to use for token counting
|
|
50
51
|
include_parent_context: Whether to include context from parent sessions
|
|
51
52
|
current_query: Current user query for relevance-based context selection
|
|
53
|
+
max_history: Maximum number of messages to include for conversation strategy
|
|
52
54
|
|
|
53
55
|
Returns:
|
|
54
56
|
A list of message dictionaries suitable for LLM API calls
|
|
@@ -72,7 +74,7 @@ async def build_prompt_from_session(
|
|
|
72
74
|
elif strategy == PromptStrategy.TOOL_FOCUSED:
|
|
73
75
|
return await _build_tool_focused_prompt(session)
|
|
74
76
|
elif strategy == PromptStrategy.CONVERSATION:
|
|
75
|
-
return await _build_conversation_prompt(session, max_history
|
|
77
|
+
return await _build_conversation_prompt(session, max_history)
|
|
76
78
|
elif strategy == PromptStrategy.HIERARCHICAL:
|
|
77
79
|
return await _build_hierarchical_prompt(session, include_parent_context)
|
|
78
80
|
else:
|
|
@@ -112,7 +114,7 @@ async def _build_minimal_prompt(session: Session) -> List[Dict[str, str]]:
|
|
|
112
114
|
|
|
113
115
|
if assistant_msg is None:
|
|
114
116
|
# Only the user message exists so far
|
|
115
|
-
return [{"role": "user", "content": first_user.message}] if first_user else []
|
|
117
|
+
return [{"role": "user", "content": _extract_content(first_user.message)}] if first_user else []
|
|
116
118
|
|
|
117
119
|
# Children of that assistant
|
|
118
120
|
children = [
|
|
@@ -126,11 +128,7 @@ async def _build_minimal_prompt(session: Session) -> List[Dict[str, str]]:
|
|
|
126
128
|
# Assemble prompt
|
|
127
129
|
prompt: List[Dict[str, str]] = []
|
|
128
130
|
if first_user:
|
|
129
|
-
|
|
130
|
-
user_content = first_user.message
|
|
131
|
-
if isinstance(user_content, dict) and "content" in user_content:
|
|
132
|
-
user_content = user_content["content"]
|
|
133
|
-
prompt.append({"role": "user", "content": user_content})
|
|
131
|
+
prompt.append({"role": "user", "content": _extract_content(first_user.message)})
|
|
134
132
|
|
|
135
133
|
# ALWAYS add the assistant marker - but strip its free text
|
|
136
134
|
prompt.append({"role": "assistant", "content": None})
|
|
@@ -166,6 +164,24 @@ async def _build_minimal_prompt(session: Session) -> List[Dict[str, str]]:
|
|
|
166
164
|
return prompt
|
|
167
165
|
|
|
168
166
|
|
|
167
|
+
def _extract_content(message: Any) -> str:
|
|
168
|
+
"""
|
|
169
|
+
Extract content string from a message that could be a string or dict.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
message: The message content (string, dict, or other)
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
The extracted content as a string
|
|
176
|
+
"""
|
|
177
|
+
if isinstance(message, str):
|
|
178
|
+
return message
|
|
179
|
+
elif isinstance(message, dict) and "content" in message:
|
|
180
|
+
return message["content"]
|
|
181
|
+
else:
|
|
182
|
+
return str(message)
|
|
183
|
+
|
|
184
|
+
|
|
169
185
|
async def _build_task_focused_prompt(session: Session) -> List[Dict[str, str]]:
|
|
170
186
|
"""
|
|
171
187
|
Build a task-focused prompt.
|
|
@@ -201,17 +217,11 @@ async def _build_task_focused_prompt(session: Session) -> List[Dict[str, str]]:
|
|
|
201
217
|
prompt = []
|
|
202
218
|
|
|
203
219
|
# Always include the first user message (the main task)
|
|
204
|
-
|
|
205
|
-
if isinstance(first_content, dict) and "content" in first_content:
|
|
206
|
-
first_content = first_content["content"]
|
|
207
|
-
prompt.append({"role": "user", "content": first_content})
|
|
220
|
+
prompt.append({"role": "user", "content": _extract_content(first_user.message)})
|
|
208
221
|
|
|
209
222
|
# Include the latest user message if different from the first
|
|
210
223
|
if latest_user and latest_user.id != first_user.id:
|
|
211
|
-
|
|
212
|
-
if isinstance(latest_content, dict) and "content" in latest_content:
|
|
213
|
-
latest_content = latest_content["content"]
|
|
214
|
-
prompt.append({"role": "user", "content": latest_content})
|
|
224
|
+
prompt.append({"role": "user", "content": _extract_content(latest_user.message)})
|
|
215
225
|
|
|
216
226
|
# Include assistant response placeholder
|
|
217
227
|
if assistant_msg:
|
|
@@ -274,10 +284,7 @@ async def _build_tool_focused_prompt(session: Session) -> List[Dict[str, str]]:
|
|
|
274
284
|
prompt = []
|
|
275
285
|
|
|
276
286
|
# Include user message
|
|
277
|
-
|
|
278
|
-
if isinstance(user_content, dict) and "content" in user_content:
|
|
279
|
-
user_content = user_content["content"]
|
|
280
|
-
prompt.append({"role": "user", "content": user_content})
|
|
287
|
+
prompt.append({"role": "user", "content": _extract_content(latest_user.message)})
|
|
281
288
|
|
|
282
289
|
# Include assistant placeholder
|
|
283
290
|
if assistant_msg:
|
|
@@ -334,17 +341,17 @@ async def _build_conversation_prompt(
|
|
|
334
341
|
|
|
335
342
|
# Build the conversation history
|
|
336
343
|
prompt = []
|
|
337
|
-
for msg in recent_messages:
|
|
344
|
+
for i, msg in enumerate(recent_messages):
|
|
338
345
|
role = "user" if msg.source == EventSource.USER else "assistant"
|
|
339
|
-
content = msg.message
|
|
346
|
+
content = _extract_content(msg.message)
|
|
340
347
|
|
|
341
|
-
#
|
|
342
|
-
if
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
content
|
|
348
|
+
# For the last assistant message, set content to None and add tool calls
|
|
349
|
+
if (role == "assistant" and
|
|
350
|
+
msg == recent_messages[-1] and
|
|
351
|
+
msg.source != EventSource.USER):
|
|
352
|
+
|
|
353
|
+
# Add the message first with None content
|
|
354
|
+
prompt.append({"role": role, "content": None})
|
|
348
355
|
|
|
349
356
|
# Add tool call results for this assistant message
|
|
350
357
|
tool_calls = [
|
|
@@ -352,9 +359,6 @@ async def _build_conversation_prompt(
|
|
|
352
359
|
if e.type == EventType.TOOL_CALL and e.metadata.get("parent_event_id") == msg.id
|
|
353
360
|
]
|
|
354
361
|
|
|
355
|
-
# Add the message first, then tools
|
|
356
|
-
prompt.append({"role": role, "content": content})
|
|
357
|
-
|
|
358
362
|
# Add tool results
|
|
359
363
|
for tc in tool_calls:
|
|
360
364
|
if isinstance(tc.message, dict):
|
|
@@ -366,11 +370,9 @@ async def _build_conversation_prompt(
|
|
|
366
370
|
"name": tool_name,
|
|
367
371
|
"content": json.dumps(tool_result, default=str),
|
|
368
372
|
})
|
|
369
|
-
|
|
370
|
-
#
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
prompt.append({"role": role, "content": content})
|
|
373
|
+
else:
|
|
374
|
+
# Regular message
|
|
375
|
+
prompt.append({"role": role, "content": content})
|
|
374
376
|
|
|
375
377
|
return prompt
|
|
376
378
|
|
|
@@ -391,32 +393,38 @@ async def _build_hierarchical_prompt(
|
|
|
391
393
|
|
|
392
394
|
# If parent context is enabled and session has a parent
|
|
393
395
|
if include_parent_context and session.parent_id:
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
if parent:
|
|
400
|
-
# Find the most recent summary in parent
|
|
401
|
-
summary_event = next(
|
|
402
|
-
(e for e in reversed(parent.events)
|
|
403
|
-
if e.type == EventType.SUMMARY),
|
|
404
|
-
None
|
|
405
|
-
)
|
|
396
|
+
try:
|
|
397
|
+
# Get the storage backend and create store
|
|
398
|
+
backend = get_backend()
|
|
399
|
+
store = ChukSessionsStore(backend)
|
|
400
|
+
parent = await store.get(session.parent_id)
|
|
406
401
|
|
|
407
|
-
if
|
|
408
|
-
#
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
402
|
+
if parent:
|
|
403
|
+
# Find the most recent summary in parent
|
|
404
|
+
summary_event = next(
|
|
405
|
+
(e for e in reversed(parent.events)
|
|
406
|
+
if e.type == EventType.SUMMARY),
|
|
407
|
+
None
|
|
408
|
+
)
|
|
409
|
+
|
|
410
|
+
if summary_event:
|
|
411
|
+
# Extract summary content
|
|
412
|
+
summary_content = summary_event.message
|
|
413
|
+
if isinstance(summary_content, dict) and "note" in summary_content:
|
|
414
|
+
summary_content = summary_content["note"]
|
|
415
|
+
elif isinstance(summary_content, dict) and "content" in summary_content:
|
|
416
|
+
summary_content = summary_content["content"]
|
|
417
|
+
else:
|
|
418
|
+
summary_content = str(summary_content)
|
|
419
|
+
|
|
420
|
+
# Add parent context at the beginning
|
|
421
|
+
prompt.insert(0, {
|
|
422
|
+
"role": "system",
|
|
423
|
+
"content": f"Context from previous conversation: {summary_content}"
|
|
424
|
+
})
|
|
425
|
+
except Exception as e:
|
|
426
|
+
# If we can't load parent context, just continue with minimal prompt
|
|
427
|
+
logger.warning(f"Could not load parent context for session {session.parent_id}: {e}")
|
|
420
428
|
|
|
421
429
|
return prompt
|
|
422
430
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# chuk_ai_session_manager/session_storage.py
|
|
1
|
+
# src/chuk_ai_session_manager/session_storage.py
|
|
2
2
|
"""
|
|
3
3
|
CHUK Sessions storage backend for AI Session Manager.
|
|
4
4
|
|
|
@@ -11,7 +11,18 @@ import json
|
|
|
11
11
|
import logging
|
|
12
12
|
from typing import Any, Dict, List, Optional
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
# Import the correct class from chuk_sessions
|
|
15
|
+
# We need to determine the correct import based on what's actually available
|
|
16
|
+
try:
|
|
17
|
+
from chuk_sessions import SessionManager as ChukSessionManager
|
|
18
|
+
except ImportError as e:
|
|
19
|
+
raise ImportError(
|
|
20
|
+
f"Cannot import SessionManager from chuk_sessions: {e}\n"
|
|
21
|
+
"Please ensure chuk_sessions is properly installed with: uv add chuk-sessions\n"
|
|
22
|
+
"Or check the available classes in chuk_sessions by running:\n"
|
|
23
|
+
"python -c \"import chuk_sessions; print(dir(chuk_sessions))\""
|
|
24
|
+
) from e
|
|
25
|
+
|
|
15
26
|
from chuk_ai_session_manager.models.session import Session
|
|
16
27
|
|
|
17
28
|
logger = logging.getLogger(__name__)
|
|
@@ -45,19 +56,21 @@ class SessionStorage:
|
|
|
45
56
|
return self._cache[session_id]
|
|
46
57
|
|
|
47
58
|
try:
|
|
48
|
-
|
|
49
|
-
return None
|
|
50
|
-
|
|
59
|
+
# Get session info from CHUK Sessions
|
|
51
60
|
info = await self.chuk.get_session_info(session_id)
|
|
52
61
|
if not info:
|
|
53
62
|
return None
|
|
54
63
|
|
|
64
|
+
# Check if it's an AI session manager session
|
|
55
65
|
custom_metadata = info.get('custom_metadata', {})
|
|
56
|
-
|
|
66
|
+
if custom_metadata.get('session_type') != 'ai_session_manager':
|
|
67
|
+
return None
|
|
57
68
|
|
|
69
|
+
ai_session_json = custom_metadata.get('ai_session_data')
|
|
58
70
|
if not ai_session_json:
|
|
59
71
|
return None
|
|
60
72
|
|
|
73
|
+
# Parse the JSON data
|
|
61
74
|
session_data = json.loads(ai_session_json)
|
|
62
75
|
ai_session = Session.model_validate(session_data)
|
|
63
76
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: chuk-ai-session-manager
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: Session manager for AI applications
|
|
5
5
|
Requires-Python: >=3.11
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -14,6 +14,7 @@ Requires-Dist: redis>=4.0.0; extra == "redis"
|
|
|
14
14
|
Provides-Extra: dev
|
|
15
15
|
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
16
16
|
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
17
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
17
18
|
Requires-Dist: redis>=4.0.0; extra == "dev"
|
|
18
19
|
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
19
20
|
Requires-Dist: isort>=5.12.0; extra == "dev"
|
|
@@ -311,36 +312,6 @@ pip install chuk-ai-session-manager
|
|
|
311
312
|
| **Production Ready** | Requires additional work | Built for production |
|
|
312
313
|
| **Learning Curve** | Steep | 5 minutes to productivity |
|
|
313
314
|
|
|
314
|
-
## 🚀 Migration Guides
|
|
315
|
-
|
|
316
|
-
### From LangChain Memory
|
|
317
|
-
```python
|
|
318
|
-
# Old LangChain way
|
|
319
|
-
from langchain.memory import ConversationBufferMemory
|
|
320
|
-
memory = ConversationBufferMemory()
|
|
321
|
-
memory.save_context({"input": "Hi"}, {"output": "Hello"})
|
|
322
|
-
|
|
323
|
-
# New CHUK way (much simpler!)
|
|
324
|
-
from chuk_ai_session_manager import track_conversation
|
|
325
|
-
await track_conversation("Hi", "Hello")
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
### From Manual Session Management
|
|
329
|
-
```python
|
|
330
|
-
# Old manual way
|
|
331
|
-
conversations = {}
|
|
332
|
-
def save_conversation(user_id, message, response):
|
|
333
|
-
if user_id not in conversations:
|
|
334
|
-
conversations[user_id] = []
|
|
335
|
-
conversations[user_id].append({"user": message, "ai": response})
|
|
336
|
-
|
|
337
|
-
# New CHUK way
|
|
338
|
-
from chuk_ai_session_manager import SessionManager
|
|
339
|
-
sm = SessionManager(session_id=user_id)
|
|
340
|
-
await sm.user_says(message)
|
|
341
|
-
await sm.ai_responds(response)
|
|
342
|
-
```
|
|
343
|
-
|
|
344
315
|
## 📖 More Examples
|
|
345
316
|
|
|
346
317
|
Check out the `/examples` directory for complete working examples:
|
|
@@ -362,16 +333,9 @@ Check out the `/examples` directory for complete working examples:
|
|
|
362
333
|
- ✅ Complete conversation analytics and observability
|
|
363
334
|
- ✅ Framework-agnostic solution that works with any LLM library
|
|
364
335
|
|
|
365
|
-
**Consider alternatives if you:**
|
|
366
|
-
- ❌ Only need basic in-memory conversation history
|
|
367
|
-
- ❌ Are locked into a specific framework (LangChain, etc.)
|
|
368
|
-
- ❌ Don't need cost tracking or analytics
|
|
369
|
-
- ❌ Are building simple, stateless AI applications
|
|
370
|
-
|
|
371
336
|
## 🤝 Community & Support
|
|
372
337
|
|
|
373
338
|
- 📖 **Documentation**: [Full docs with tutorials](link-to-docs)
|
|
374
|
-
- 💬 **Discord**: Join our community for help and discussions
|
|
375
339
|
- 🐛 **Issues**: Report bugs on GitHub
|
|
376
340
|
- 💡 **Feature Requests**: Suggest new features
|
|
377
341
|
- 📧 **Support**: enterprise@chuk.dev for production support
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
chuk_ai_session_manager/__init__.py,sha256=Kg8_fqwkV88b0f9H8Q45MDqaZdHHTo3Wf0Oko89DhHg,10217
|
|
2
|
+
chuk_ai_session_manager/exceptions.py,sha256=WqrrUZuOAiUmz7tKnSnk0y222U_nV9a8LyaXLayn2fg,4420
|
|
3
|
+
chuk_ai_session_manager/infinite_conversation.py,sha256=7j3caMnsX27M5rjj4oOkqiy_2AfcupWwsAWRflnKiSo,12092
|
|
4
|
+
chuk_ai_session_manager/sample_tools.py,sha256=U-jTGveTJ95uSnA4jB30fJQJG3K-TGxN9jcOY6qVHZQ,8179
|
|
5
|
+
chuk_ai_session_manager/session_aware_tool_processor.py,sha256=iVe3d-qfp5QGkdNrgfZeRYoOjd8nLZ0g6K7HW1thFE8,7274
|
|
6
|
+
chuk_ai_session_manager/session_manager.py,sha256=AauyE2XTqAM53fGxpJD73nVwxB3cWH0jMlBXakYTAD0,29195
|
|
7
|
+
chuk_ai_session_manager/session_prompt_builder.py,sha256=Jeg_MWses_hFtHtDL7ZQl6EdSNVmVIIrLDrWEoPumfM,17613
|
|
8
|
+
chuk_ai_session_manager/session_storage.py,sha256=wb6SPwOKVoov2cGmK6DuorpPM7xbjnCRHrA6VTvG7GE,6427
|
|
9
|
+
chuk_ai_session_manager/api/__init__.py,sha256=Lo_BoDW2rSn0Zw-CbjahOxc6ykjjTpucxHZo5FA2Gnc,41
|
|
10
|
+
chuk_ai_session_manager/api/simple_api.py,sha256=aUDH2KzkI_M2eUiW9DAQo2KmUYaVa5u25-gSIF3PzGM,10504
|
|
11
|
+
chuk_ai_session_manager/models/__init__.py,sha256=H1rRuDQDRf821JPUWUn5Zgwvc5BAqcEGekkHEmX-IgE,1167
|
|
12
|
+
chuk_ai_session_manager/models/event_source.py,sha256=mn_D16sXMa6nAX-5BzssygJPz6VF24GRe-3IaH7bTnI,196
|
|
13
|
+
chuk_ai_session_manager/models/event_type.py,sha256=TPPvAz-PlXVtrwXDNVFVnhdt1yEfgDGmKDGt8ArYcTk,275
|
|
14
|
+
chuk_ai_session_manager/models/session.py,sha256=yUPuu6yt8HbBebkIejpgSDGTLhZjtLOxFRkWG2C6XKM,12193
|
|
15
|
+
chuk_ai_session_manager/models/session_event.py,sha256=RTghC9_sDHzD8qdgEYCoclJzpVo8cE6B2f7jlsDOVr0,9006
|
|
16
|
+
chuk_ai_session_manager/models/session_metadata.py,sha256=KFG7lc_E0BQTP2OD9Y529elVGJXppDUMqz8vVONW0rw,1510
|
|
17
|
+
chuk_ai_session_manager/models/session_run.py,sha256=uhMM4-WSrqOUsiWQPnyakInd-foZhxI-YnSHSWiZZwE,4369
|
|
18
|
+
chuk_ai_session_manager/models/token_usage.py,sha256=M9Qwmeb2woILaSRwA2SIAiG-sIwC3cL_1H-y3NjW5Ik,11436
|
|
19
|
+
chuk_ai_session_manager-0.4.1.dist-info/METADATA,sha256=0PvxzwOw2HG8XujmXkMInNVAlrhF149rxyLzXTUROAg,11136
|
|
20
|
+
chuk_ai_session_manager-0.4.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
21
|
+
chuk_ai_session_manager-0.4.1.dist-info/top_level.txt,sha256=5RinqD0v-niHuLYePUREX4gEWTlrpgtUg0RfexVRBMk,24
|
|
22
|
+
chuk_ai_session_manager-0.4.1.dist-info/RECORD,,
|
|
File without changes
|