chuk-ai-session-manager 0.7__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.dist-info → chuk_ai_session_manager-0.8.dist-info}/METADATA +78 -2
- chuk_ai_session_manager-0.8.dist-info/RECORD +45 -0
- {chuk_ai_session_manager-0.7.dist-info → chuk_ai_session_manager-0.8.dist-info}/WHEEL +1 -1
- chuk_ai_session_manager-0.7.dist-info/RECORD +0 -22
- {chuk_ai_session_manager-0.7.dist-info → chuk_ai_session_manager-0.8.dist-info}/top_level.txt +0 -0
|
@@ -10,9 +10,9 @@ and hierarchical context awareness.
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
import json
|
|
12
12
|
import logging
|
|
13
|
-
from typing import List, Dict, Any, Optional,
|
|
13
|
+
from typing import List, Dict, Any, Optional, Union
|
|
14
14
|
from enum import Enum
|
|
15
|
-
import asyncio
|
|
15
|
+
import asyncio
|
|
16
16
|
|
|
17
17
|
from chuk_ai_session_manager.models.session import Session
|
|
18
18
|
from chuk_ai_session_manager.models.event_type import EventType
|
|
@@ -22,11 +22,13 @@ from chuk_ai_session_manager.session_storage import get_backend, ChukSessionsSto
|
|
|
22
22
|
|
|
23
23
|
logger = logging.getLogger(__name__)
|
|
24
24
|
|
|
25
|
+
|
|
25
26
|
class PromptStrategy(str, Enum):
|
|
26
27
|
"""Different strategies for building prompts."""
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
|
|
29
|
+
MINIMAL = "minimal" # Original minimal approach
|
|
30
|
+
TASK_FOCUSED = "task" # Focus on the task with minimal context
|
|
31
|
+
TOOL_FOCUSED = "tool" # Emphasize tool usage and results
|
|
30
32
|
CONVERSATION = "conversation" # Include more conversation history
|
|
31
33
|
HIERARCHICAL = "hierarchical" # Include parent session context
|
|
32
34
|
|
|
@@ -38,11 +40,11 @@ async def build_prompt_from_session(
|
|
|
38
40
|
model: str = "gpt-3.5-turbo",
|
|
39
41
|
include_parent_context: bool = False,
|
|
40
42
|
current_query: Optional[str] = None,
|
|
41
|
-
max_history: int = 5 # Add this parameter for conversation strategy
|
|
43
|
+
max_history: int = 5, # Add this parameter for conversation strategy
|
|
42
44
|
) -> List[Dict[str, str]]:
|
|
43
45
|
"""
|
|
44
46
|
Build a prompt for the next LLM call from a Session asynchronously.
|
|
45
|
-
|
|
47
|
+
|
|
46
48
|
Args:
|
|
47
49
|
session: The session to build a prompt from
|
|
48
50
|
strategy: Prompt building strategy to use
|
|
@@ -51,13 +53,13 @@ async def build_prompt_from_session(
|
|
|
51
53
|
include_parent_context: Whether to include context from parent sessions
|
|
52
54
|
current_query: Current user query for relevance-based context selection
|
|
53
55
|
max_history: Maximum number of messages to include for conversation strategy
|
|
54
|
-
|
|
56
|
+
|
|
55
57
|
Returns:
|
|
56
58
|
A list of message dictionaries suitable for LLM API calls
|
|
57
59
|
"""
|
|
58
60
|
if not session.events:
|
|
59
61
|
return []
|
|
60
|
-
|
|
62
|
+
|
|
61
63
|
# Convert string strategy to enum if needed
|
|
62
64
|
if isinstance(strategy, str):
|
|
63
65
|
try:
|
|
@@ -65,7 +67,7 @@ async def build_prompt_from_session(
|
|
|
65
67
|
except ValueError:
|
|
66
68
|
logger.warning(f"Unknown strategy '{strategy}', falling back to MINIMAL")
|
|
67
69
|
strategy = PromptStrategy.MINIMAL
|
|
68
|
-
|
|
70
|
+
|
|
69
71
|
# Use the appropriate strategy
|
|
70
72
|
if strategy == PromptStrategy.MINIMAL:
|
|
71
73
|
return await _build_minimal_prompt(session)
|
|
@@ -85,7 +87,7 @@ async def build_prompt_from_session(
|
|
|
85
87
|
async def _build_minimal_prompt(session: Session) -> List[Dict[str, str]]:
|
|
86
88
|
"""
|
|
87
89
|
Build a minimal prompt from a session.
|
|
88
|
-
|
|
90
|
+
|
|
89
91
|
This follows the original implementation's approach:
|
|
90
92
|
- Include the first USER message (task)
|
|
91
93
|
- Include the latest assistant MESSAGE with content set to None
|
|
@@ -111,10 +113,14 @@ async def _build_minimal_prompt(session: Session) -> List[Dict[str, str]]:
|
|
|
111
113
|
),
|
|
112
114
|
None,
|
|
113
115
|
)
|
|
114
|
-
|
|
116
|
+
|
|
115
117
|
if assistant_msg is None:
|
|
116
118
|
# Only the user message exists so far
|
|
117
|
-
return
|
|
119
|
+
return (
|
|
120
|
+
[{"role": "user", "content": _extract_content(first_user.message)}]
|
|
121
|
+
if first_user
|
|
122
|
+
else []
|
|
123
|
+
)
|
|
118
124
|
|
|
119
125
|
# Children of that assistant
|
|
120
126
|
children = [
|
|
@@ -138,7 +144,9 @@ async def _build_minimal_prompt(session: Session) -> List[Dict[str, str]]:
|
|
|
138
144
|
# Extract relevant information from the tool call
|
|
139
145
|
# Handle both new and legacy formats
|
|
140
146
|
if isinstance(tc.message, dict):
|
|
141
|
-
tool_name = tc.message.get(
|
|
147
|
+
tool_name = tc.message.get(
|
|
148
|
+
"tool_name", tc.message.get("tool", "unknown")
|
|
149
|
+
)
|
|
142
150
|
tool_result = tc.message.get("result", {})
|
|
143
151
|
else:
|
|
144
152
|
# Legacy format or unexpected type
|
|
@@ -167,10 +175,10 @@ async def _build_minimal_prompt(session: Session) -> List[Dict[str, str]]:
|
|
|
167
175
|
def _extract_content(message: Any) -> str:
|
|
168
176
|
"""
|
|
169
177
|
Extract content string from a message that could be a string or dict.
|
|
170
|
-
|
|
178
|
+
|
|
171
179
|
Args:
|
|
172
180
|
message: The message content (string, dict, or other)
|
|
173
|
-
|
|
181
|
+
|
|
174
182
|
Returns:
|
|
175
183
|
The extracted content as a string
|
|
176
184
|
"""
|
|
@@ -185,7 +193,7 @@ def _extract_content(message: Any) -> str:
|
|
|
185
193
|
async def _build_task_focused_prompt(session: Session) -> List[Dict[str, str]]:
|
|
186
194
|
"""
|
|
187
195
|
Build a task-focused prompt.
|
|
188
|
-
|
|
196
|
+
|
|
189
197
|
This strategy emphasizes the original task and latest context:
|
|
190
198
|
- Includes the first USER message as the main task
|
|
191
199
|
- Includes the most recent USER message for current context
|
|
@@ -193,16 +201,17 @@ async def _build_task_focused_prompt(session: Session) -> List[Dict[str, str]]:
|
|
|
193
201
|
"""
|
|
194
202
|
# Get first and most recent user messages
|
|
195
203
|
user_messages = [
|
|
196
|
-
e
|
|
204
|
+
e
|
|
205
|
+
for e in session.events
|
|
197
206
|
if e.type == EventType.MESSAGE and e.source == EventSource.USER
|
|
198
207
|
]
|
|
199
|
-
|
|
208
|
+
|
|
200
209
|
if not user_messages:
|
|
201
210
|
return []
|
|
202
|
-
|
|
211
|
+
|
|
203
212
|
first_user = user_messages[0]
|
|
204
213
|
latest_user = user_messages[-1] if len(user_messages) > 1 else None
|
|
205
|
-
|
|
214
|
+
|
|
206
215
|
# Latest assistant MESSAGE
|
|
207
216
|
assistant_msg = next(
|
|
208
217
|
(
|
|
@@ -212,52 +221,62 @@ async def _build_task_focused_prompt(session: Session) -> List[Dict[str, str]]:
|
|
|
212
221
|
),
|
|
213
222
|
None,
|
|
214
223
|
)
|
|
215
|
-
|
|
224
|
+
|
|
216
225
|
# Build prompt
|
|
217
226
|
prompt = []
|
|
218
|
-
|
|
227
|
+
|
|
219
228
|
# Always include the first user message (the main task)
|
|
220
229
|
prompt.append({"role": "user", "content": _extract_content(first_user.message)})
|
|
221
|
-
|
|
230
|
+
|
|
222
231
|
# Include the latest user message if different from the first
|
|
223
232
|
if latest_user and latest_user.id != first_user.id:
|
|
224
|
-
prompt.append(
|
|
225
|
-
|
|
233
|
+
prompt.append(
|
|
234
|
+
{"role": "user", "content": _extract_content(latest_user.message)}
|
|
235
|
+
)
|
|
236
|
+
|
|
226
237
|
# Include assistant response placeholder
|
|
227
238
|
if assistant_msg:
|
|
228
239
|
prompt.append({"role": "assistant", "content": None})
|
|
229
|
-
|
|
240
|
+
|
|
230
241
|
# Find successful tool calls
|
|
231
242
|
children = [
|
|
232
|
-
e
|
|
243
|
+
e
|
|
244
|
+
for e in session.events
|
|
233
245
|
if e.metadata.get("parent_event_id") == assistant_msg.id
|
|
234
246
|
]
|
|
235
247
|
tool_calls = [c for c in children if c.type == EventType.TOOL_CALL]
|
|
236
|
-
|
|
248
|
+
|
|
237
249
|
# Only include successful tool results
|
|
238
250
|
for tc in tool_calls:
|
|
239
251
|
# Extract and check if result indicates success
|
|
240
252
|
if isinstance(tc.message, dict):
|
|
241
|
-
tool_name = tc.message.get(
|
|
253
|
+
tool_name = tc.message.get(
|
|
254
|
+
"tool_name", tc.message.get("tool", "unknown")
|
|
255
|
+
)
|
|
242
256
|
tool_result = tc.message.get("result", {})
|
|
243
|
-
|
|
257
|
+
|
|
244
258
|
# Skip error results
|
|
245
|
-
if
|
|
259
|
+
if (
|
|
260
|
+
isinstance(tool_result, dict)
|
|
261
|
+
and tool_result.get("status") == "error"
|
|
262
|
+
):
|
|
246
263
|
continue
|
|
247
|
-
|
|
248
|
-
prompt.append(
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
264
|
+
|
|
265
|
+
prompt.append(
|
|
266
|
+
{
|
|
267
|
+
"role": "tool",
|
|
268
|
+
"name": tool_name,
|
|
269
|
+
"content": json.dumps(tool_result, default=str),
|
|
270
|
+
}
|
|
271
|
+
)
|
|
272
|
+
|
|
254
273
|
return prompt
|
|
255
274
|
|
|
256
275
|
|
|
257
276
|
async def _build_tool_focused_prompt(session: Session) -> List[Dict[str, str]]:
|
|
258
277
|
"""
|
|
259
278
|
Build a tool-focused prompt.
|
|
260
|
-
|
|
279
|
+
|
|
261
280
|
This strategy emphasizes tool usage:
|
|
262
281
|
- Includes the latest user query
|
|
263
282
|
- Includes detailed information about tool calls and results
|
|
@@ -265,132 +284,149 @@ async def _build_tool_focused_prompt(session: Session) -> List[Dict[str, str]]:
|
|
|
265
284
|
"""
|
|
266
285
|
# Get the latest user message
|
|
267
286
|
latest_user = next(
|
|
268
|
-
(
|
|
269
|
-
|
|
270
|
-
|
|
287
|
+
(
|
|
288
|
+
e
|
|
289
|
+
for e in reversed(session.events)
|
|
290
|
+
if e.type == EventType.MESSAGE and e.source == EventSource.USER
|
|
291
|
+
),
|
|
292
|
+
None,
|
|
271
293
|
)
|
|
272
|
-
|
|
294
|
+
|
|
273
295
|
if not latest_user:
|
|
274
296
|
return []
|
|
275
|
-
|
|
297
|
+
|
|
276
298
|
# Get the latest assistant message
|
|
277
299
|
assistant_msg = next(
|
|
278
|
-
(
|
|
279
|
-
|
|
280
|
-
|
|
300
|
+
(
|
|
301
|
+
ev
|
|
302
|
+
for ev in reversed(session.events)
|
|
303
|
+
if ev.type == EventType.MESSAGE and ev.source != EventSource.USER
|
|
304
|
+
),
|
|
305
|
+
None,
|
|
281
306
|
)
|
|
282
|
-
|
|
307
|
+
|
|
283
308
|
# Build prompt
|
|
284
309
|
prompt = []
|
|
285
|
-
|
|
310
|
+
|
|
286
311
|
# Include user message
|
|
287
312
|
prompt.append({"role": "user", "content": _extract_content(latest_user.message)})
|
|
288
|
-
|
|
313
|
+
|
|
289
314
|
# Include assistant placeholder
|
|
290
315
|
if assistant_msg:
|
|
291
316
|
prompt.append({"role": "assistant", "content": None})
|
|
292
|
-
|
|
317
|
+
|
|
293
318
|
# Get all tool calls for this assistant
|
|
294
319
|
children = [
|
|
295
|
-
e
|
|
320
|
+
e
|
|
321
|
+
for e in session.events
|
|
296
322
|
if e.metadata.get("parent_event_id") == assistant_msg.id
|
|
297
323
|
]
|
|
298
324
|
tool_calls = [c for c in children if c.type == EventType.TOOL_CALL]
|
|
299
|
-
|
|
325
|
+
|
|
300
326
|
# Add all tool calls with status information
|
|
301
327
|
for tc in tool_calls:
|
|
302
328
|
if isinstance(tc.message, dict):
|
|
303
|
-
tool_name = tc.message.get(
|
|
329
|
+
tool_name = tc.message.get(
|
|
330
|
+
"tool_name", tc.message.get("tool", "unknown")
|
|
331
|
+
)
|
|
304
332
|
tool_result = tc.message.get("result", {})
|
|
305
333
|
error = tc.message.get("error", None)
|
|
306
|
-
|
|
334
|
+
|
|
307
335
|
# Include status information in the tool response
|
|
308
336
|
content = tool_result
|
|
309
337
|
if error:
|
|
310
338
|
content = {"error": error, "details": tool_result}
|
|
311
|
-
|
|
312
|
-
prompt.append(
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
339
|
+
|
|
340
|
+
prompt.append(
|
|
341
|
+
{
|
|
342
|
+
"role": "tool",
|
|
343
|
+
"name": tool_name,
|
|
344
|
+
"content": json.dumps(content, default=str),
|
|
345
|
+
}
|
|
346
|
+
)
|
|
347
|
+
|
|
318
348
|
return prompt
|
|
319
349
|
|
|
320
350
|
|
|
321
351
|
async def _build_conversation_prompt(
|
|
322
|
-
session: Session,
|
|
323
|
-
max_history: int = 5
|
|
352
|
+
session: Session, max_history: int = 5
|
|
324
353
|
) -> List[Dict[str, str]]:
|
|
325
354
|
"""
|
|
326
355
|
Build a conversation-style prompt with recent history.
|
|
327
|
-
|
|
356
|
+
|
|
328
357
|
This strategy creates a more natural conversation:
|
|
329
358
|
- Includes up to max_history recent messages in order
|
|
330
359
|
- Preserves conversation flow
|
|
331
360
|
- Still handles tool calls appropriately
|
|
332
361
|
"""
|
|
333
362
|
# Get relevant message events
|
|
334
|
-
message_events = [
|
|
335
|
-
|
|
336
|
-
if e.type == EventType.MESSAGE
|
|
337
|
-
]
|
|
338
|
-
|
|
363
|
+
message_events = [e for e in session.events if e.type == EventType.MESSAGE]
|
|
364
|
+
|
|
339
365
|
# Take the most recent messages
|
|
340
|
-
recent_messages =
|
|
341
|
-
|
|
366
|
+
recent_messages = (
|
|
367
|
+
message_events[-max_history:]
|
|
368
|
+
if len(message_events) > max_history
|
|
369
|
+
else message_events
|
|
370
|
+
)
|
|
371
|
+
|
|
342
372
|
# Build the conversation history
|
|
343
373
|
prompt = []
|
|
344
374
|
for i, msg in enumerate(recent_messages):
|
|
345
375
|
role = "user" if msg.source == EventSource.USER else "assistant"
|
|
346
376
|
content = _extract_content(msg.message)
|
|
347
|
-
|
|
377
|
+
|
|
348
378
|
# For the last assistant message, set content to None and add tool calls
|
|
349
|
-
if (
|
|
350
|
-
|
|
351
|
-
msg
|
|
352
|
-
|
|
379
|
+
if (
|
|
380
|
+
role == "assistant"
|
|
381
|
+
and msg == recent_messages[-1]
|
|
382
|
+
and msg.source != EventSource.USER
|
|
383
|
+
):
|
|
353
384
|
# Add the message first with None content
|
|
354
385
|
prompt.append({"role": role, "content": None})
|
|
355
|
-
|
|
386
|
+
|
|
356
387
|
# Add tool call results for this assistant message
|
|
357
388
|
tool_calls = [
|
|
358
|
-
e
|
|
359
|
-
|
|
389
|
+
e
|
|
390
|
+
for e in session.events
|
|
391
|
+
if e.type == EventType.TOOL_CALL
|
|
392
|
+
and e.metadata.get("parent_event_id") == msg.id
|
|
360
393
|
]
|
|
361
|
-
|
|
394
|
+
|
|
362
395
|
# Add tool results
|
|
363
396
|
for tc in tool_calls:
|
|
364
397
|
if isinstance(tc.message, dict):
|
|
365
|
-
tool_name = tc.message.get(
|
|
398
|
+
tool_name = tc.message.get(
|
|
399
|
+
"tool_name", tc.message.get("tool", "unknown")
|
|
400
|
+
)
|
|
366
401
|
tool_result = tc.message.get("result", {})
|
|
367
|
-
|
|
368
|
-
prompt.append(
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
402
|
+
|
|
403
|
+
prompt.append(
|
|
404
|
+
{
|
|
405
|
+
"role": "tool",
|
|
406
|
+
"name": tool_name,
|
|
407
|
+
"content": json.dumps(tool_result, default=str),
|
|
408
|
+
}
|
|
409
|
+
)
|
|
373
410
|
else:
|
|
374
411
|
# Regular message
|
|
375
412
|
prompt.append({"role": role, "content": content})
|
|
376
|
-
|
|
413
|
+
|
|
377
414
|
return prompt
|
|
378
415
|
|
|
379
416
|
|
|
380
417
|
async def _build_hierarchical_prompt(
|
|
381
|
-
session: Session,
|
|
382
|
-
include_parent_context: bool = True
|
|
418
|
+
session: Session, include_parent_context: bool = True
|
|
383
419
|
) -> List[Dict[str, str]]:
|
|
384
420
|
"""
|
|
385
421
|
Build a prompt that includes hierarchical context.
|
|
386
|
-
|
|
422
|
+
|
|
387
423
|
This strategy leverages the session hierarchy:
|
|
388
424
|
- Starts with the minimal prompt
|
|
389
425
|
- Includes summaries from parent sessions if available
|
|
390
426
|
"""
|
|
391
427
|
# Start with the minimal prompt
|
|
392
428
|
prompt = await _build_minimal_prompt(session)
|
|
393
|
-
|
|
429
|
+
|
|
394
430
|
# If parent context is enabled and session has a parent
|
|
395
431
|
if include_parent_context and session.parent_id:
|
|
396
432
|
try:
|
|
@@ -398,36 +434,44 @@ async def _build_hierarchical_prompt(
|
|
|
398
434
|
backend = get_backend()
|
|
399
435
|
store = ChukSessionsStore(backend)
|
|
400
436
|
parent = await store.get(session.parent_id)
|
|
401
|
-
|
|
437
|
+
|
|
402
438
|
if parent:
|
|
403
439
|
# Find the most recent summary in parent
|
|
404
440
|
summary_event = next(
|
|
405
|
-
(e for e in reversed(parent.events)
|
|
406
|
-
|
|
407
|
-
None
|
|
441
|
+
(e for e in reversed(parent.events) if e.type == EventType.SUMMARY),
|
|
442
|
+
None,
|
|
408
443
|
)
|
|
409
|
-
|
|
444
|
+
|
|
410
445
|
if summary_event:
|
|
411
446
|
# Extract summary content
|
|
412
447
|
summary_content = summary_event.message
|
|
413
448
|
if isinstance(summary_content, dict) and "note" in summary_content:
|
|
414
449
|
summary_content = summary_content["note"]
|
|
415
|
-
elif
|
|
450
|
+
elif (
|
|
451
|
+
isinstance(summary_content, dict)
|
|
452
|
+
and "content" in summary_content
|
|
453
|
+
):
|
|
416
454
|
summary_content = summary_content["content"]
|
|
417
455
|
else:
|
|
418
456
|
summary_content = str(summary_content)
|
|
419
|
-
|
|
457
|
+
|
|
420
458
|
# Add parent context at the beginning
|
|
421
|
-
prompt.insert(
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
459
|
+
prompt.insert(
|
|
460
|
+
0,
|
|
461
|
+
{
|
|
462
|
+
"role": "system",
|
|
463
|
+
"content": f"Context from previous conversation: {summary_content}",
|
|
464
|
+
},
|
|
465
|
+
)
|
|
425
466
|
except Exception as e:
|
|
426
467
|
# If we can't load parent context, just continue with minimal prompt
|
|
427
|
-
logger.warning(
|
|
428
|
-
|
|
468
|
+
logger.warning(
|
|
469
|
+
f"Could not load parent context for session {session.parent_id}: {e}"
|
|
470
|
+
)
|
|
471
|
+
|
|
429
472
|
return prompt
|
|
430
473
|
|
|
474
|
+
|
|
431
475
|
async def truncate_prompt_to_token_limit(
|
|
432
476
|
prompt: List[Dict[str, str]],
|
|
433
477
|
max_tokens: int,
|
|
@@ -448,7 +492,9 @@ async def truncate_prompt_to_token_limit(
|
|
|
448
492
|
|
|
449
493
|
# ------------------------------------------------------------------ #
|
|
450
494
|
# quick overall count
|
|
451
|
-
text = "\n".join(
|
|
495
|
+
text = "\n".join(
|
|
496
|
+
f"{m.get('role', 'unknown')}: {m.get('content') or ''}" for m in prompt
|
|
497
|
+
)
|
|
452
498
|
total = TokenUsage.count_tokens(text, model)
|
|
453
499
|
total = await total if asyncio.iscoroutine(total) else total
|
|
454
500
|
if total <= max_tokens:
|
|
@@ -456,9 +502,15 @@ async def truncate_prompt_to_token_limit(
|
|
|
456
502
|
|
|
457
503
|
# ------------------------------------------------------------------ #
|
|
458
504
|
# decide which messages to keep
|
|
459
|
-
first_user_idx = next(
|
|
505
|
+
first_user_idx = next(
|
|
506
|
+
(i for i, m in enumerate(prompt) if m["role"] == "user"), None
|
|
507
|
+
)
|
|
460
508
|
last_asst_idx = next(
|
|
461
|
-
(
|
|
509
|
+
(
|
|
510
|
+
len(prompt) - 1 - i
|
|
511
|
+
for i, m in enumerate(reversed(prompt))
|
|
512
|
+
if m["role"] == "assistant"
|
|
513
|
+
),
|
|
462
514
|
None,
|
|
463
515
|
)
|
|
464
516
|
|
|
@@ -481,4 +533,4 @@ async def truncate_prompt_to_token_limit(
|
|
|
481
533
|
if first_tool:
|
|
482
534
|
kept.append(first_tool)
|
|
483
535
|
|
|
484
|
-
return kept
|
|
536
|
+
return kept
|