jaf-py 2.5.10__py3-none-any.whl → 2.5.12__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 +154 -57
- jaf/a2a/__init__.py +42 -21
- jaf/a2a/agent.py +79 -126
- jaf/a2a/agent_card.py +87 -78
- jaf/a2a/client.py +30 -66
- jaf/a2a/examples/client_example.py +12 -12
- jaf/a2a/examples/integration_example.py +38 -47
- jaf/a2a/examples/server_example.py +56 -53
- jaf/a2a/memory/__init__.py +0 -4
- jaf/a2a/memory/cleanup.py +28 -21
- jaf/a2a/memory/factory.py +155 -133
- jaf/a2a/memory/providers/composite.py +21 -26
- jaf/a2a/memory/providers/in_memory.py +89 -83
- jaf/a2a/memory/providers/postgres.py +117 -115
- jaf/a2a/memory/providers/redis.py +128 -121
- jaf/a2a/memory/serialization.py +77 -87
- jaf/a2a/memory/tests/run_comprehensive_tests.py +112 -83
- jaf/a2a/memory/tests/test_cleanup.py +211 -94
- jaf/a2a/memory/tests/test_serialization.py +73 -68
- jaf/a2a/memory/tests/test_stress_concurrency.py +186 -133
- jaf/a2a/memory/tests/test_task_lifecycle.py +138 -120
- jaf/a2a/memory/types.py +91 -53
- jaf/a2a/protocol.py +95 -125
- jaf/a2a/server.py +90 -118
- jaf/a2a/standalone_client.py +30 -43
- jaf/a2a/tests/__init__.py +16 -33
- jaf/a2a/tests/run_tests.py +17 -53
- jaf/a2a/tests/test_agent.py +40 -140
- jaf/a2a/tests/test_client.py +54 -117
- jaf/a2a/tests/test_integration.py +28 -82
- jaf/a2a/tests/test_protocol.py +54 -139
- jaf/a2a/tests/test_types.py +50 -136
- jaf/a2a/types.py +58 -34
- jaf/cli.py +21 -41
- jaf/core/__init__.py +7 -1
- jaf/core/agent_tool.py +93 -72
- jaf/core/analytics.py +257 -207
- jaf/core/checkpoint.py +223 -0
- jaf/core/composition.py +249 -235
- jaf/core/engine.py +817 -519
- jaf/core/errors.py +55 -42
- jaf/core/guardrails.py +276 -202
- jaf/core/handoff.py +47 -31
- jaf/core/parallel_agents.py +69 -75
- jaf/core/performance.py +75 -73
- jaf/core/proxy.py +43 -44
- jaf/core/proxy_helpers.py +24 -27
- jaf/core/regeneration.py +220 -129
- jaf/core/state.py +68 -66
- jaf/core/streaming.py +115 -108
- jaf/core/tool_results.py +111 -101
- jaf/core/tools.py +114 -116
- jaf/core/tracing.py +310 -210
- jaf/core/types.py +403 -151
- jaf/core/workflows.py +209 -168
- jaf/exceptions.py +46 -38
- jaf/memory/__init__.py +1 -6
- jaf/memory/approval_storage.py +54 -77
- jaf/memory/factory.py +4 -4
- jaf/memory/providers/in_memory.py +216 -180
- jaf/memory/providers/postgres.py +216 -146
- jaf/memory/providers/redis.py +173 -116
- jaf/memory/types.py +70 -51
- jaf/memory/utils.py +36 -34
- jaf/plugins/__init__.py +12 -12
- jaf/plugins/base.py +105 -96
- jaf/policies/__init__.py +0 -1
- jaf/policies/handoff.py +37 -46
- jaf/policies/validation.py +76 -52
- jaf/providers/__init__.py +6 -3
- jaf/providers/mcp.py +97 -51
- jaf/providers/model.py +475 -283
- jaf/server/__init__.py +1 -1
- jaf/server/main.py +7 -11
- jaf/server/server.py +514 -359
- jaf/server/types.py +208 -52
- jaf/utils/__init__.py +17 -18
- jaf/utils/attachments.py +111 -116
- jaf/utils/document_processor.py +175 -174
- jaf/visualization/__init__.py +1 -1
- jaf/visualization/example.py +111 -110
- jaf/visualization/functional_core.py +46 -71
- jaf/visualization/graphviz.py +154 -189
- jaf/visualization/imperative_shell.py +7 -16
- jaf/visualization/types.py +8 -4
- {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/METADATA +2 -2
- jaf_py-2.5.12.dist-info/RECORD +97 -0
- jaf_py-2.5.10.dist-info/RECORD +0 -96
- {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/WHEEL +0 -0
- {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/entry_points.txt +0 -0
- {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/licenses/LICENSE +0 -0
- {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/top_level.txt +0 -0
jaf/core/checkpoint.py
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Checkpoint functionality for the JAF framework.
|
|
3
|
+
|
|
4
|
+
This module implements conversation checkpointing where a conversation can be
|
|
5
|
+
saved at a specific point, removing all subsequent messages without regeneration.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import time
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import Any, TypeVar, Optional, List
|
|
11
|
+
|
|
12
|
+
from .types import (
|
|
13
|
+
RunState,
|
|
14
|
+
RunConfig,
|
|
15
|
+
CheckpointRequest,
|
|
16
|
+
CheckpointContext,
|
|
17
|
+
MessageId,
|
|
18
|
+
Message,
|
|
19
|
+
find_message_index,
|
|
20
|
+
get_message_by_id,
|
|
21
|
+
generate_run_id,
|
|
22
|
+
generate_trace_id,
|
|
23
|
+
)
|
|
24
|
+
from ..memory.types import Success, Failure
|
|
25
|
+
|
|
26
|
+
Ctx = TypeVar("Ctx")
|
|
27
|
+
Out = TypeVar("Out")
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass(frozen=True)
|
|
31
|
+
class CheckpointResult:
|
|
32
|
+
"""Result of a checkpoint operation."""
|
|
33
|
+
|
|
34
|
+
checkpoint_id: str
|
|
35
|
+
conversation_id: str
|
|
36
|
+
original_message_count: int
|
|
37
|
+
checkpointed_at_index: int
|
|
38
|
+
checkpointed_message_id: MessageId
|
|
39
|
+
messages: List[Message]
|
|
40
|
+
execution_time_ms: int
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
async def checkpoint_conversation(
|
|
44
|
+
checkpoint_request: CheckpointRequest, config: RunConfig[Ctx]
|
|
45
|
+
) -> CheckpointResult:
|
|
46
|
+
"""
|
|
47
|
+
Checkpoint a conversation after a specific message ID.
|
|
48
|
+
|
|
49
|
+
This function:
|
|
50
|
+
1. Loads the full conversation from memory
|
|
51
|
+
2. Finds the message to checkpoint after
|
|
52
|
+
3. Truncates the conversation AFTER that point (keeps the checkpoint message)
|
|
53
|
+
4. Stores the checkpointed conversation
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
checkpoint_request: The checkpoint request containing conversation_id and message_id
|
|
57
|
+
config: The run configuration
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
CheckpointResult with the checkpointed conversation data
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
ValueError: If memory provider is not configured or conversation not found
|
|
64
|
+
"""
|
|
65
|
+
start_time = time.time()
|
|
66
|
+
|
|
67
|
+
if not config.memory or not config.memory.provider or not config.conversation_id:
|
|
68
|
+
raise ValueError("Checkpoint requires memory provider and conversation_id to be configured")
|
|
69
|
+
|
|
70
|
+
# Load the conversation from memory
|
|
71
|
+
conversation_result = await config.memory.provider.get_conversation(
|
|
72
|
+
checkpoint_request.conversation_id
|
|
73
|
+
)
|
|
74
|
+
if isinstance(conversation_result, Failure):
|
|
75
|
+
raise ValueError(f"Failed to load conversation: {conversation_result.error}")
|
|
76
|
+
|
|
77
|
+
conversation_memory = conversation_result.data
|
|
78
|
+
if not conversation_memory:
|
|
79
|
+
raise ValueError(f"Conversation {checkpoint_request.conversation_id} not found")
|
|
80
|
+
|
|
81
|
+
# Convert tuple back to list for processing
|
|
82
|
+
original_messages = list(conversation_memory.messages)
|
|
83
|
+
|
|
84
|
+
# Find the message to checkpoint after
|
|
85
|
+
checkpoint_message = get_message_by_id(original_messages, checkpoint_request.message_id)
|
|
86
|
+
if not checkpoint_message:
|
|
87
|
+
raise ValueError(f"Message {checkpoint_request.message_id} not found in conversation")
|
|
88
|
+
|
|
89
|
+
# Get the index of the checkpoint message
|
|
90
|
+
checkpoint_index = find_message_index(original_messages, checkpoint_request.message_id)
|
|
91
|
+
if checkpoint_index is None:
|
|
92
|
+
raise ValueError(f"Failed to find index for message {checkpoint_request.message_id}")
|
|
93
|
+
|
|
94
|
+
# Truncate messages AFTER the checkpoint message (keep the checkpoint message itself)
|
|
95
|
+
# This is the KEY difference from regeneration which truncates FROM the message
|
|
96
|
+
checkpointed_messages = original_messages[
|
|
97
|
+
: checkpoint_index + 1
|
|
98
|
+
] # +1 to include the checkpoint message
|
|
99
|
+
|
|
100
|
+
print(f"[JAF:CHECKPOINT] Checkpointed conversation to {len(checkpointed_messages)} messages")
|
|
101
|
+
print(
|
|
102
|
+
f"[JAF:CHECKPOINT] Original: {len(original_messages)}, Removed: {len(original_messages) - len(checkpointed_messages)}"
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Create checkpoint context
|
|
106
|
+
checkpoint_context = CheckpointContext(
|
|
107
|
+
original_message_count=len(original_messages),
|
|
108
|
+
checkpointed_at_index=checkpoint_index,
|
|
109
|
+
checkpointed_message_id=checkpoint_request.message_id,
|
|
110
|
+
checkpoint_id=f"chk_{int(time.time() * 1000)}_{checkpoint_request.message_id}",
|
|
111
|
+
timestamp=int(time.time() * 1000),
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Prepare metadata (simpler than regeneration - no audit trail needed)
|
|
115
|
+
def serialize_metadata(metadata):
|
|
116
|
+
import json
|
|
117
|
+
import datetime
|
|
118
|
+
|
|
119
|
+
def json_serializer(obj):
|
|
120
|
+
if isinstance(obj, datetime.datetime):
|
|
121
|
+
return obj.isoformat()
|
|
122
|
+
elif isinstance(obj, datetime.date):
|
|
123
|
+
return obj.isoformat()
|
|
124
|
+
elif hasattr(obj, "__dict__"):
|
|
125
|
+
return obj.__dict__
|
|
126
|
+
return str(obj)
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
json_str = json.dumps(metadata, default=json_serializer)
|
|
130
|
+
return json.loads(json_str)
|
|
131
|
+
except Exception as e:
|
|
132
|
+
print(f"[JAF:CHECKPOINT] Warning: Metadata serialization failed: {e}")
|
|
133
|
+
return {
|
|
134
|
+
"checkpoint_truncated": True,
|
|
135
|
+
"checkpoint_point": str(checkpoint_request.message_id),
|
|
136
|
+
"original_message_count": len(original_messages),
|
|
137
|
+
"checkpointed_at_index": checkpoint_index,
|
|
138
|
+
"checkpointed_messages": len(checkpointed_messages),
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
metadata = serialize_metadata(
|
|
142
|
+
{
|
|
143
|
+
**conversation_memory.metadata,
|
|
144
|
+
"checkpoint_truncated": True,
|
|
145
|
+
"checkpoint_point": str(checkpoint_request.message_id),
|
|
146
|
+
"original_message_count": len(original_messages),
|
|
147
|
+
"checkpointed_at_index": checkpoint_index,
|
|
148
|
+
"checkpointed_messages": len(checkpointed_messages),
|
|
149
|
+
"checkpoint_id": checkpoint_context.checkpoint_id,
|
|
150
|
+
"checkpoint_timestamp": checkpoint_context.timestamp,
|
|
151
|
+
}
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
# Store the checkpointed conversation
|
|
155
|
+
store_result = await config.memory.provider.store_messages(
|
|
156
|
+
checkpoint_request.conversation_id, checkpointed_messages, metadata
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
if isinstance(store_result, Failure):
|
|
160
|
+
raise ValueError(f"Failed to store checkpointed conversation: {store_result.error}")
|
|
161
|
+
|
|
162
|
+
print(
|
|
163
|
+
f"[JAF:CHECKPOINT] Successfully checkpointed conversation {checkpoint_request.conversation_id}"
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
execution_time_ms = int((time.time() - start_time) * 1000)
|
|
167
|
+
|
|
168
|
+
return CheckpointResult(
|
|
169
|
+
checkpoint_id=checkpoint_context.checkpoint_id,
|
|
170
|
+
conversation_id=checkpoint_request.conversation_id,
|
|
171
|
+
original_message_count=len(original_messages),
|
|
172
|
+
checkpointed_at_index=checkpoint_index,
|
|
173
|
+
checkpointed_message_id=checkpoint_request.message_id,
|
|
174
|
+
messages=checkpointed_messages,
|
|
175
|
+
execution_time_ms=execution_time_ms,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
async def get_checkpoint_history(
|
|
180
|
+
conversation_id: str, config: RunConfig[Ctx]
|
|
181
|
+
) -> Optional[List[dict]]:
|
|
182
|
+
"""
|
|
183
|
+
Get checkpoint history for a conversation.
|
|
184
|
+
|
|
185
|
+
Note: Unlike regeneration, we don't maintain a checkpoint_points list in metadata.
|
|
186
|
+
This function returns the last checkpoint information if available.
|
|
187
|
+
|
|
188
|
+
Args:
|
|
189
|
+
conversation_id: The conversation ID
|
|
190
|
+
config: The run configuration
|
|
191
|
+
|
|
192
|
+
Returns:
|
|
193
|
+
List of checkpoint metadata or empty list if none found
|
|
194
|
+
"""
|
|
195
|
+
if not config.memory or not config.memory.provider:
|
|
196
|
+
return None
|
|
197
|
+
|
|
198
|
+
try:
|
|
199
|
+
conversation_result = await config.memory.provider.get_conversation(conversation_id)
|
|
200
|
+
if hasattr(conversation_result, "data") and conversation_result.data:
|
|
201
|
+
metadata = conversation_result.data.metadata
|
|
202
|
+
|
|
203
|
+
# Check if there's checkpoint metadata
|
|
204
|
+
if metadata.get("checkpoint_truncated"):
|
|
205
|
+
checkpoint_data = {
|
|
206
|
+
"checkpoint_id": metadata.get("checkpoint_id", ""),
|
|
207
|
+
"checkpoint_point": metadata.get("checkpoint_point", ""),
|
|
208
|
+
"timestamp": metadata.get("checkpoint_timestamp", 0),
|
|
209
|
+
"original_message_count": metadata.get("original_message_count", 0),
|
|
210
|
+
"checkpointed_at_index": metadata.get("checkpointed_at_index", 0),
|
|
211
|
+
"checkpointed_messages": metadata.get("checkpointed_messages", 0),
|
|
212
|
+
}
|
|
213
|
+
print(f"[JAF:CHECKPOINT] Retrieved checkpoint data for {conversation_id}")
|
|
214
|
+
return [checkpoint_data]
|
|
215
|
+
else:
|
|
216
|
+
print(f"[JAF:CHECKPOINT] No checkpoint data found for {conversation_id}")
|
|
217
|
+
return []
|
|
218
|
+
else:
|
|
219
|
+
print(f"[JAF:CHECKPOINT] No conversation data found for {conversation_id}")
|
|
220
|
+
return []
|
|
221
|
+
except Exception as e:
|
|
222
|
+
print(f"[JAF:CHECKPOINT] Failed to get checkpoint history: {e}")
|
|
223
|
+
return []
|