jaf-py 2.5.9__py3-none-any.whl → 2.5.11__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.
Files changed (92) hide show
  1. jaf/__init__.py +154 -57
  2. jaf/a2a/__init__.py +42 -21
  3. jaf/a2a/agent.py +79 -126
  4. jaf/a2a/agent_card.py +87 -78
  5. jaf/a2a/client.py +30 -66
  6. jaf/a2a/examples/client_example.py +12 -12
  7. jaf/a2a/examples/integration_example.py +38 -47
  8. jaf/a2a/examples/server_example.py +56 -53
  9. jaf/a2a/memory/__init__.py +0 -4
  10. jaf/a2a/memory/cleanup.py +28 -21
  11. jaf/a2a/memory/factory.py +155 -133
  12. jaf/a2a/memory/providers/composite.py +21 -26
  13. jaf/a2a/memory/providers/in_memory.py +89 -83
  14. jaf/a2a/memory/providers/postgres.py +117 -115
  15. jaf/a2a/memory/providers/redis.py +128 -121
  16. jaf/a2a/memory/serialization.py +77 -87
  17. jaf/a2a/memory/tests/run_comprehensive_tests.py +112 -83
  18. jaf/a2a/memory/tests/test_cleanup.py +211 -94
  19. jaf/a2a/memory/tests/test_serialization.py +73 -68
  20. jaf/a2a/memory/tests/test_stress_concurrency.py +186 -133
  21. jaf/a2a/memory/tests/test_task_lifecycle.py +138 -120
  22. jaf/a2a/memory/types.py +91 -53
  23. jaf/a2a/protocol.py +95 -125
  24. jaf/a2a/server.py +90 -118
  25. jaf/a2a/standalone_client.py +30 -43
  26. jaf/a2a/tests/__init__.py +16 -33
  27. jaf/a2a/tests/run_tests.py +17 -53
  28. jaf/a2a/tests/test_agent.py +40 -140
  29. jaf/a2a/tests/test_client.py +54 -117
  30. jaf/a2a/tests/test_integration.py +28 -82
  31. jaf/a2a/tests/test_protocol.py +54 -139
  32. jaf/a2a/tests/test_types.py +50 -136
  33. jaf/a2a/types.py +58 -34
  34. jaf/cli.py +21 -41
  35. jaf/core/__init__.py +7 -1
  36. jaf/core/agent_tool.py +93 -72
  37. jaf/core/analytics.py +257 -207
  38. jaf/core/checkpoint.py +223 -0
  39. jaf/core/composition.py +249 -235
  40. jaf/core/engine.py +817 -519
  41. jaf/core/errors.py +55 -42
  42. jaf/core/guardrails.py +276 -202
  43. jaf/core/handoff.py +47 -31
  44. jaf/core/parallel_agents.py +69 -75
  45. jaf/core/performance.py +75 -73
  46. jaf/core/proxy.py +43 -44
  47. jaf/core/proxy_helpers.py +24 -27
  48. jaf/core/regeneration.py +220 -129
  49. jaf/core/state.py +68 -66
  50. jaf/core/streaming.py +115 -108
  51. jaf/core/tool_results.py +111 -101
  52. jaf/core/tools.py +114 -116
  53. jaf/core/tracing.py +269 -210
  54. jaf/core/types.py +371 -151
  55. jaf/core/workflows.py +209 -168
  56. jaf/exceptions.py +46 -38
  57. jaf/memory/__init__.py +1 -6
  58. jaf/memory/approval_storage.py +54 -77
  59. jaf/memory/factory.py +4 -4
  60. jaf/memory/providers/in_memory.py +216 -180
  61. jaf/memory/providers/postgres.py +216 -146
  62. jaf/memory/providers/redis.py +173 -116
  63. jaf/memory/types.py +70 -51
  64. jaf/memory/utils.py +36 -34
  65. jaf/plugins/__init__.py +12 -12
  66. jaf/plugins/base.py +105 -96
  67. jaf/policies/__init__.py +0 -1
  68. jaf/policies/handoff.py +37 -46
  69. jaf/policies/validation.py +76 -52
  70. jaf/providers/__init__.py +6 -3
  71. jaf/providers/mcp.py +97 -51
  72. jaf/providers/model.py +361 -280
  73. jaf/server/__init__.py +1 -1
  74. jaf/server/main.py +7 -11
  75. jaf/server/server.py +514 -359
  76. jaf/server/types.py +208 -52
  77. jaf/utils/__init__.py +17 -18
  78. jaf/utils/attachments.py +111 -116
  79. jaf/utils/document_processor.py +175 -174
  80. jaf/visualization/__init__.py +1 -1
  81. jaf/visualization/example.py +111 -110
  82. jaf/visualization/functional_core.py +46 -71
  83. jaf/visualization/graphviz.py +154 -189
  84. jaf/visualization/imperative_shell.py +7 -16
  85. jaf/visualization/types.py +8 -4
  86. {jaf_py-2.5.9.dist-info → jaf_py-2.5.11.dist-info}/METADATA +2 -2
  87. jaf_py-2.5.11.dist-info/RECORD +97 -0
  88. jaf_py-2.5.9.dist-info/RECORD +0 -96
  89. {jaf_py-2.5.9.dist-info → jaf_py-2.5.11.dist-info}/WHEEL +0 -0
  90. {jaf_py-2.5.9.dist-info → jaf_py-2.5.11.dist-info}/entry_points.txt +0 -0
  91. {jaf_py-2.5.9.dist-info → jaf_py-2.5.11.dist-info}/licenses/LICENSE +0 -0
  92. {jaf_py-2.5.9.dist-info → jaf_py-2.5.11.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 []