jaf-py 2.5.2__py3-none-any.whl → 2.5.4__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.
- jaf/__init__.py +1 -1
- jaf/core/engine.py +1 -9
- jaf/core/regeneration.py +392 -0
- jaf/core/tracing.py +196 -59
- jaf/core/types.py +109 -2
- jaf/memory/providers/in_memory.py +174 -1
- jaf/memory/providers/postgres.py +211 -1
- jaf/memory/providers/redis.py +189 -1
- jaf/memory/types.py +35 -1
- jaf/memory/utils.py +2 -0
- jaf/server/server.py +163 -0
- jaf/server/types.py +49 -1
- {jaf_py-2.5.2.dist-info → jaf_py-2.5.4.dist-info}/METADATA +4 -4
- {jaf_py-2.5.2.dist-info → jaf_py-2.5.4.dist-info}/RECORD +18 -17
- {jaf_py-2.5.2.dist-info → jaf_py-2.5.4.dist-info}/WHEEL +0 -0
- {jaf_py-2.5.2.dist-info → jaf_py-2.5.4.dist-info}/entry_points.txt +0 -0
- {jaf_py-2.5.2.dist-info → jaf_py-2.5.4.dist-info}/licenses/LICENSE +0 -0
- {jaf_py-2.5.2.dist-info → jaf_py-2.5.4.dist-info}/top_level.txt +0 -0
jaf/__init__.py
CHANGED
|
@@ -191,7 +191,7 @@ def generate_run_id() -> RunId:
|
|
|
191
191
|
"""Generate a new run ID."""
|
|
192
192
|
return create_run_id(str(uuid.uuid4()))
|
|
193
193
|
|
|
194
|
-
__version__ = "2.5.
|
|
194
|
+
__version__ = "2.5.4"
|
|
195
195
|
__all__ = [
|
|
196
196
|
# Core types and functions
|
|
197
197
|
"TraceId", "RunId", "ValidationResult", "Message", "ModelConfig",
|
jaf/core/engine.py
CHANGED
|
@@ -293,15 +293,7 @@ async def _load_conversation_history(state: RunState[Ctx], config: RunConfig[Ctx
|
|
|
293
293
|
# For HITL scenarios, append new messages to memory messages
|
|
294
294
|
# This prevents duplication when resuming from interruptions
|
|
295
295
|
if memory_messages:
|
|
296
|
-
combined_messages = memory_messages +
|
|
297
|
-
msg for msg in state.messages
|
|
298
|
-
if not any(
|
|
299
|
-
mem_msg.role == msg.role and
|
|
300
|
-
mem_msg.content == msg.content and
|
|
301
|
-
getattr(mem_msg, 'tool_calls', None) == getattr(msg, 'tool_calls', None)
|
|
302
|
-
for mem_msg in memory_messages
|
|
303
|
-
)
|
|
304
|
-
]
|
|
296
|
+
combined_messages = memory_messages + list(state.messages)
|
|
305
297
|
else:
|
|
306
298
|
combined_messages = list(state.messages)
|
|
307
299
|
|
jaf/core/regeneration.py
ADDED
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Regeneration functionality for the JAF framework.
|
|
3
|
+
|
|
4
|
+
This module implements conversation regeneration where a specific message can be
|
|
5
|
+
regenerated, removing all subsequent messages and creating a new conversation path.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
from dataclasses import replace
|
|
10
|
+
from typing import Any, TypeVar, Optional
|
|
11
|
+
|
|
12
|
+
from .types import (
|
|
13
|
+
RunState, RunConfig, RunResult,
|
|
14
|
+
RegenerationRequest, RegenerationContext,
|
|
15
|
+
MessageId, Message, ErrorOutcome, ModelBehaviorError,
|
|
16
|
+
find_message_index, truncate_messages_after, get_message_by_id,
|
|
17
|
+
generate_run_id, generate_trace_id
|
|
18
|
+
)
|
|
19
|
+
from .engine import run as engine_run
|
|
20
|
+
from ..memory.types import Success, Failure
|
|
21
|
+
|
|
22
|
+
Ctx = TypeVar('Ctx')
|
|
23
|
+
Out = TypeVar('Out')
|
|
24
|
+
|
|
25
|
+
async def regenerate_conversation(
|
|
26
|
+
regeneration_request: RegenerationRequest,
|
|
27
|
+
config: RunConfig[Ctx],
|
|
28
|
+
context: Ctx,
|
|
29
|
+
agent_name: str
|
|
30
|
+
) -> RunResult[Out]:
|
|
31
|
+
"""
|
|
32
|
+
Regenerate a conversation from a specific message ID.
|
|
33
|
+
|
|
34
|
+
This function:
|
|
35
|
+
1. Loads the full conversation from memory
|
|
36
|
+
2. Finds the message to regenerate from
|
|
37
|
+
3. Truncates the conversation at that point
|
|
38
|
+
4. Creates a new RunState with truncated conversation
|
|
39
|
+
5. Executes the regeneration through the normal engine flow
|
|
40
|
+
6. Updates memory with the new conversation path
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
regeneration_request: The regeneration request containing conversation_id and message_id
|
|
44
|
+
config: The run configuration
|
|
45
|
+
context: The context for the regeneration
|
|
46
|
+
agent_name: The name of the agent to use for regeneration
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
RunResult with the regenerated conversation outcome
|
|
50
|
+
"""
|
|
51
|
+
if not config.memory or not config.memory.provider or not config.conversation_id:
|
|
52
|
+
return RunResult(
|
|
53
|
+
final_state=RunState(
|
|
54
|
+
run_id=generate_run_id(),
|
|
55
|
+
trace_id=generate_trace_id(),
|
|
56
|
+
messages=[],
|
|
57
|
+
current_agent_name=agent_name,
|
|
58
|
+
context=context,
|
|
59
|
+
turn_count=0
|
|
60
|
+
),
|
|
61
|
+
outcome=ErrorOutcome(error=ModelBehaviorError(
|
|
62
|
+
detail="Regeneration requires memory provider and conversation_id to be configured"
|
|
63
|
+
))
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Load the conversation from memory
|
|
67
|
+
conversation_result = await config.memory.provider.get_conversation(regeneration_request.conversation_id)
|
|
68
|
+
if isinstance(conversation_result, Failure):
|
|
69
|
+
return RunResult(
|
|
70
|
+
final_state=RunState(
|
|
71
|
+
run_id=generate_run_id(),
|
|
72
|
+
trace_id=generate_trace_id(),
|
|
73
|
+
messages=[],
|
|
74
|
+
current_agent_name=agent_name,
|
|
75
|
+
context=context,
|
|
76
|
+
turn_count=0
|
|
77
|
+
),
|
|
78
|
+
outcome=ErrorOutcome(error=ModelBehaviorError(
|
|
79
|
+
detail=f"Failed to load conversation: {conversation_result.error}"
|
|
80
|
+
))
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
conversation_memory = conversation_result.data
|
|
84
|
+
if not conversation_memory:
|
|
85
|
+
return RunResult(
|
|
86
|
+
final_state=RunState(
|
|
87
|
+
run_id=generate_run_id(),
|
|
88
|
+
trace_id=generate_trace_id(),
|
|
89
|
+
messages=[],
|
|
90
|
+
current_agent_name=agent_name,
|
|
91
|
+
context=context,
|
|
92
|
+
turn_count=0
|
|
93
|
+
),
|
|
94
|
+
outcome=ErrorOutcome(error=ModelBehaviorError(
|
|
95
|
+
detail=f"Conversation {regeneration_request.conversation_id} not found"
|
|
96
|
+
))
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
# Convert tuple back to list for processing
|
|
100
|
+
original_messages = list(conversation_memory.messages)
|
|
101
|
+
|
|
102
|
+
# Find the message to regenerate from
|
|
103
|
+
regenerate_message = get_message_by_id(original_messages, regeneration_request.message_id)
|
|
104
|
+
if not regenerate_message:
|
|
105
|
+
return RunResult(
|
|
106
|
+
final_state=RunState(
|
|
107
|
+
run_id=generate_run_id(),
|
|
108
|
+
trace_id=generate_trace_id(),
|
|
109
|
+
messages=original_messages,
|
|
110
|
+
current_agent_name=agent_name,
|
|
111
|
+
context=context,
|
|
112
|
+
turn_count=len([m for m in original_messages if (m.role.value if hasattr(m.role, 'value') else m.role) == 'assistant'])
|
|
113
|
+
),
|
|
114
|
+
outcome=ErrorOutcome(error=ModelBehaviorError(
|
|
115
|
+
detail=f"Message {regeneration_request.message_id} not found in conversation"
|
|
116
|
+
))
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
# Get the index of the message to regenerate
|
|
120
|
+
regenerate_index = find_message_index(original_messages, regeneration_request.message_id)
|
|
121
|
+
if regenerate_index is None:
|
|
122
|
+
return RunResult(
|
|
123
|
+
final_state=RunState(
|
|
124
|
+
run_id=generate_run_id(),
|
|
125
|
+
trace_id=generate_trace_id(),
|
|
126
|
+
messages=original_messages,
|
|
127
|
+
current_agent_name=agent_name,
|
|
128
|
+
context=context,
|
|
129
|
+
turn_count=len([m for m in original_messages if (m.role.value if hasattr(m.role, 'value') else m.role) == 'assistant'])
|
|
130
|
+
),
|
|
131
|
+
outcome=ErrorOutcome(error=ModelBehaviorError(
|
|
132
|
+
detail=f"Failed to find index for message {regeneration_request.message_id}"
|
|
133
|
+
))
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
def determine_regeneration_type(messages, regenerate_index, context):
|
|
137
|
+
"""Determine if this is pure regeneration or edit scenario."""
|
|
138
|
+
if context and context.get("replace_user_message"):
|
|
139
|
+
return "edit"
|
|
140
|
+
|
|
141
|
+
regenerate_message = messages[regenerate_index]
|
|
142
|
+
if regenerate_message.role in ['assistant', 'ASSISTANT']:
|
|
143
|
+
for i in range(regenerate_index - 1, -1, -1):
|
|
144
|
+
if messages[i].role in ['user', 'USER']:
|
|
145
|
+
return "pure"
|
|
146
|
+
return "edit"
|
|
147
|
+
|
|
148
|
+
# Determine regeneration type
|
|
149
|
+
regen_type = determine_regeneration_type(original_messages, regenerate_index, regeneration_request.context or {})
|
|
150
|
+
print(f"[JAF:REGENERATION] Detected regeneration type: {regen_type}")
|
|
151
|
+
|
|
152
|
+
if regen_type == "pure":
|
|
153
|
+
# For pure regeneration, find the user message that started this conversation turn
|
|
154
|
+
user_message_index = None
|
|
155
|
+
for i in range(regenerate_index - 1, -1, -1):
|
|
156
|
+
if original_messages[i].role in ['user', 'USER']:
|
|
157
|
+
user_message_index = i
|
|
158
|
+
break
|
|
159
|
+
|
|
160
|
+
if user_message_index is not None:
|
|
161
|
+
# Truncate AFTER the user message (keeps user message, removes tool calls/outputs)
|
|
162
|
+
truncated_messages = original_messages[:user_message_index + 1]
|
|
163
|
+
print(f"[JAF:REGENERATION] Pure regeneration: truncated to user message at index {user_message_index}")
|
|
164
|
+
else:
|
|
165
|
+
truncated_messages = original_messages[:regenerate_index]
|
|
166
|
+
print(f"[JAF:REGENERATION] Pure regeneration fallback: no user message found")
|
|
167
|
+
else:
|
|
168
|
+
# Edit regeneration: truncate at the specified point and add replacement query
|
|
169
|
+
truncated_messages = original_messages[:regenerate_index]
|
|
170
|
+
|
|
171
|
+
if (regeneration_request.context and
|
|
172
|
+
regeneration_request.context.get("replace_user_message")):
|
|
173
|
+
|
|
174
|
+
from .types import ContentRole, Message
|
|
175
|
+
replacement_user_message = Message(
|
|
176
|
+
role=ContentRole.USER,
|
|
177
|
+
content=regeneration_request.context.get("replace_user_message")
|
|
178
|
+
)
|
|
179
|
+
truncated_messages.append(replacement_user_message)
|
|
180
|
+
print(f"[JAF:REGENERATION] Edit regeneration: replaced user query with: {regeneration_request.context.get('replace_user_message')}")
|
|
181
|
+
|
|
182
|
+
print(f"[JAF:REGENERATION] Truncated conversation to {len(truncated_messages)} messages")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
print(f"[JAF:REGENERATION] About to store {len(truncated_messages)} truncated messages to memory")
|
|
186
|
+
|
|
187
|
+
def serialize_metadata(metadata):
|
|
188
|
+
import json
|
|
189
|
+
import datetime
|
|
190
|
+
|
|
191
|
+
def json_serializer(obj):
|
|
192
|
+
if isinstance(obj, datetime.datetime):
|
|
193
|
+
return obj.isoformat()
|
|
194
|
+
elif isinstance(obj, datetime.date):
|
|
195
|
+
return obj.isoformat()
|
|
196
|
+
elif hasattr(obj, '__dict__'):
|
|
197
|
+
return obj.__dict__
|
|
198
|
+
return str(obj)
|
|
199
|
+
|
|
200
|
+
try:
|
|
201
|
+
json_str = json.dumps(metadata, default=json_serializer)
|
|
202
|
+
return json.loads(json_str)
|
|
203
|
+
except Exception as e:
|
|
204
|
+
print(f"[JAF:REGENERATION] Warning: Metadata serialization failed: {e}")
|
|
205
|
+
return {
|
|
206
|
+
"regeneration_truncated": True,
|
|
207
|
+
"regeneration_point": str(regeneration_request.message_id),
|
|
208
|
+
"original_message_count": len(original_messages),
|
|
209
|
+
"truncated_at_index": regenerate_index,
|
|
210
|
+
"turn_count": len([m for m in truncated_messages if (m.role.value if hasattr(m.role, 'value') else m.role) == 'assistant'])
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
metadata = serialize_metadata({
|
|
214
|
+
**conversation_memory.metadata,
|
|
215
|
+
"regeneration_truncated": True,
|
|
216
|
+
"regeneration_point": str(regeneration_request.message_id),
|
|
217
|
+
"original_message_count": len(original_messages),
|
|
218
|
+
"truncated_at_index": regenerate_index,
|
|
219
|
+
"turn_count": len([m for m in truncated_messages if (m.role.value if hasattr(m.role, 'value') else m.role) == 'assistant'])
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
store_result = await config.memory.provider.store_messages(
|
|
223
|
+
regeneration_request.conversation_id,
|
|
224
|
+
truncated_messages,
|
|
225
|
+
metadata
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
print(f"[JAF:REGENERATION] Store result type: {type(store_result)}")
|
|
229
|
+
if isinstance(store_result, Failure):
|
|
230
|
+
print(f"[JAF:REGENERATION] Store failed with error: {store_result.error}")
|
|
231
|
+
return RunResult(
|
|
232
|
+
final_state=RunState(
|
|
233
|
+
run_id=generate_run_id(),
|
|
234
|
+
trace_id=generate_trace_id(),
|
|
235
|
+
messages=original_messages,
|
|
236
|
+
current_agent_name=agent_name,
|
|
237
|
+
context=context,
|
|
238
|
+
turn_count=len([m for m in original_messages if (m.role.value if hasattr(m.role, 'value') else m.role) == 'assistant'])
|
|
239
|
+
),
|
|
240
|
+
outcome=ErrorOutcome(error=ModelBehaviorError(
|
|
241
|
+
detail=f"Failed to store truncated conversation: {store_result.error}"
|
|
242
|
+
))
|
|
243
|
+
)
|
|
244
|
+
else:
|
|
245
|
+
print(f"[JAF:REGENERATION] Store successful, proceeding to engine execution")
|
|
246
|
+
|
|
247
|
+
# Create regeneration context for later use
|
|
248
|
+
regeneration_context = RegenerationContext(
|
|
249
|
+
original_message_count=len(original_messages),
|
|
250
|
+
truncated_at_index=regenerate_index,
|
|
251
|
+
regenerated_message_id=regeneration_request.message_id,
|
|
252
|
+
regeneration_id=f"regen_{int(time.time() * 1000)}_{regeneration_request.message_id}",
|
|
253
|
+
timestamp=int(time.time() * 1000)
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# Calculate turn count from truncated messages
|
|
257
|
+
truncated_turn_count = len([m for m in truncated_messages if (m.role.value if hasattr(m.role, 'value') else m.role) == 'assistant'])
|
|
258
|
+
|
|
259
|
+
final_context = context
|
|
260
|
+
print(f"[JAF:REGENERATION] Using provided context: {type(context).__name__}")
|
|
261
|
+
|
|
262
|
+
# Create initial state for regeneration with truncated conversation
|
|
263
|
+
initial_state = RunState(
|
|
264
|
+
run_id=generate_run_id(),
|
|
265
|
+
trace_id=generate_trace_id(),
|
|
266
|
+
messages=[],
|
|
267
|
+
current_agent_name=agent_name,
|
|
268
|
+
context=final_context,
|
|
269
|
+
turn_count=truncated_turn_count,
|
|
270
|
+
approvals={} # Reset approvals for regeneration
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
print(f"[JAF:REGENERATION] Starting regeneration from message {regeneration_request.message_id}")
|
|
274
|
+
print(f"[JAF:REGENERATION] Original messages: {len(original_messages)}, Truncated to: {len(truncated_messages)}")
|
|
275
|
+
print(f"[JAF:REGENERATION] Regeneration context: {regeneration_context}")
|
|
276
|
+
|
|
277
|
+
# Create a modified config for regeneration that ensures memory storage
|
|
278
|
+
regeneration_config = replace(
|
|
279
|
+
config,
|
|
280
|
+
conversation_id=regeneration_request.conversation_id,
|
|
281
|
+
memory=replace(config.memory, auto_store=True, store_on_completion=True) if config.memory else None
|
|
282
|
+
)
|
|
283
|
+
|
|
284
|
+
# Execute the regeneration through the normal engine flow
|
|
285
|
+
print(f"[JAF:REGENERATION] About to execute engine with {len(truncated_messages)} messages")
|
|
286
|
+
print(f"[JAF:REGENERATION] Final message: {truncated_messages[-1] if truncated_messages else 'None'}")
|
|
287
|
+
|
|
288
|
+
result = await engine_run(initial_state, regeneration_config)
|
|
289
|
+
|
|
290
|
+
print(f"[JAF:REGENERATION] Regeneration completed with status: {result.outcome.status}")
|
|
291
|
+
if hasattr(result, 'final_state') and hasattr(result.final_state, 'messages'):
|
|
292
|
+
print(f"[JAF:REGENERATION] Final state has {len(result.final_state.messages)} messages")
|
|
293
|
+
assistant_msgs = [m for m in result.final_state.messages if m.role in ['assistant', 'ASSISTANT']]
|
|
294
|
+
print(f"[JAF:REGENERATION] Found {len(assistant_msgs)} assistant messages in result")
|
|
295
|
+
|
|
296
|
+
# After successful regeneration, mark the regeneration point and preserve metadata
|
|
297
|
+
if result.outcome.status == 'completed' and config.memory and config.memory.provider:
|
|
298
|
+
try:
|
|
299
|
+
print(f"[JAF:REGENERATION] Marking regeneration point after successful regeneration")
|
|
300
|
+
|
|
301
|
+
# Get the current conversation to preserve regeneration metadata
|
|
302
|
+
current_conv_result = await config.memory.provider.get_conversation(regeneration_request.conversation_id)
|
|
303
|
+
print(f"[JAF:REGENERATION] Retrieved conversation for preservation: {hasattr(current_conv_result, 'data') and current_conv_result.data is not None}")
|
|
304
|
+
|
|
305
|
+
if hasattr(current_conv_result, 'data') and current_conv_result.data:
|
|
306
|
+
current_metadata = current_conv_result.data.metadata
|
|
307
|
+
regeneration_points = current_metadata.get('regeneration_points', [])
|
|
308
|
+
print(f"[JAF:REGENERATION] Found {len(regeneration_points)} regeneration points in metadata before marking")
|
|
309
|
+
|
|
310
|
+
# Mark the regeneration point by calling the provider method directly
|
|
311
|
+
mark_result = await config.memory.provider.mark_regeneration_point(
|
|
312
|
+
regeneration_request.conversation_id,
|
|
313
|
+
regeneration_request.message_id,
|
|
314
|
+
{
|
|
315
|
+
"regeneration_id": regeneration_context.regeneration_id,
|
|
316
|
+
"original_message_count": len(original_messages),
|
|
317
|
+
"truncated_at_index": regenerate_index,
|
|
318
|
+
"timestamp": regeneration_context.timestamp
|
|
319
|
+
}
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
if isinstance(mark_result, Failure):
|
|
323
|
+
print(f"[JAF:REGENERATION] Warning: Failed to mark regeneration point: {mark_result.error}")
|
|
324
|
+
else:
|
|
325
|
+
print(f"[JAF:REGENERATION] Successfully marked regeneration point")
|
|
326
|
+
|
|
327
|
+
# Get the updated conversation with the new regeneration point
|
|
328
|
+
updated_conv_result = await config.memory.provider.get_conversation(regeneration_request.conversation_id)
|
|
329
|
+
if hasattr(updated_conv_result, 'data') and updated_conv_result.data:
|
|
330
|
+
updated_metadata = updated_conv_result.data.metadata
|
|
331
|
+
updated_regeneration_points = updated_metadata.get('regeneration_points', [])
|
|
332
|
+
print(f"[JAF:REGENERATION] Found {len(updated_regeneration_points)} regeneration points after marking")
|
|
333
|
+
|
|
334
|
+
# Ensure final metadata includes the regeneration points
|
|
335
|
+
final_metadata = {
|
|
336
|
+
**updated_metadata,
|
|
337
|
+
'regeneration_points': updated_regeneration_points,
|
|
338
|
+
'regeneration_count': len(updated_regeneration_points),
|
|
339
|
+
'last_regeneration': updated_regeneration_points[-1] if updated_regeneration_points else None,
|
|
340
|
+
'regeneration_preserved': True,
|
|
341
|
+
'final_preservation_timestamp': int(time.time() * 1000)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
# Store the final conversation with preserved regeneration metadata
|
|
345
|
+
await config.memory.provider.store_messages(
|
|
346
|
+
regeneration_request.conversation_id,
|
|
347
|
+
result.final_state.messages,
|
|
348
|
+
final_metadata
|
|
349
|
+
)
|
|
350
|
+
print(f"[JAF:REGENERATION] Final preservation completed with {len(updated_regeneration_points)} regeneration points")
|
|
351
|
+
else:
|
|
352
|
+
print(f"[JAF:REGENERATION] No conversation data found for preservation")
|
|
353
|
+
|
|
354
|
+
except Exception as e:
|
|
355
|
+
print(f"[JAF:REGENERATION] Warning: Failed to preserve regeneration points: {e}")
|
|
356
|
+
import traceback
|
|
357
|
+
traceback.print_exc()
|
|
358
|
+
|
|
359
|
+
return result
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
async def get_regeneration_points(
|
|
363
|
+
conversation_id: str,
|
|
364
|
+
config: RunConfig[Ctx]
|
|
365
|
+
) -> Optional[list]:
|
|
366
|
+
"""
|
|
367
|
+
Get all regeneration points for a conversation.
|
|
368
|
+
|
|
369
|
+
Args:
|
|
370
|
+
conversation_id: The conversation ID
|
|
371
|
+
config: The run configuration
|
|
372
|
+
|
|
373
|
+
Returns:
|
|
374
|
+
List of regeneration points or None if not available
|
|
375
|
+
"""
|
|
376
|
+
if not config.memory or not config.memory.provider:
|
|
377
|
+
return None
|
|
378
|
+
|
|
379
|
+
try:
|
|
380
|
+
conversation_result = await config.memory.provider.get_conversation(conversation_id)
|
|
381
|
+
if hasattr(conversation_result, 'data') and conversation_result.data:
|
|
382
|
+
metadata = conversation_result.data.metadata
|
|
383
|
+
regeneration_points = metadata.get('regeneration_points', [])
|
|
384
|
+
print(f"[JAF:REGENERATION] Retrieved {len(regeneration_points)} regeneration points for {conversation_id}")
|
|
385
|
+
return regeneration_points
|
|
386
|
+
else:
|
|
387
|
+
print(f"[JAF:REGENERATION] No conversation data found for {conversation_id}")
|
|
388
|
+
return []
|
|
389
|
+
except Exception as e:
|
|
390
|
+
print(f"[JAF:REGENERATION] Failed to get regeneration points: {e}")
|
|
391
|
+
|
|
392
|
+
return []
|