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.
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 +310 -210
  54. jaf/core/types.py +403 -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 +475 -283
  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.10.dist-info → jaf_py-2.5.12.dist-info}/METADATA +2 -2
  87. jaf_py-2.5.12.dist-info/RECORD +97 -0
  88. jaf_py-2.5.10.dist-info/RECORD +0 -96
  89. {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/WHEEL +0 -0
  90. {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/entry_points.txt +0 -0
  91. {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/licenses/LICENSE +0 -0
  92. {jaf_py-2.5.10.dist-info → jaf_py-2.5.12.dist-info}/top_level.txt +0 -0
jaf/server/types.py CHANGED
@@ -11,30 +11,40 @@ import base64
11
11
 
12
12
  from pydantic import BaseModel, Field, field_validator, model_validator
13
13
 
14
- from ..core.types import Agent, RunConfig, Attachment, MessageContentPart, get_text_content, MessageId, RegenerationRequest
14
+ from ..core.types import (
15
+ Agent,
16
+ RunConfig,
17
+ Attachment,
18
+ MessageContentPart,
19
+ get_text_content,
20
+ MessageId,
21
+ RegenerationRequest,
22
+ )
15
23
  from ..memory.types import MemoryProvider
16
24
 
17
- Ctx = TypeVar('Ctx')
25
+ Ctx = TypeVar("Ctx")
26
+
18
27
 
19
28
  # Pydantic models for attachments to work with HTTP API
20
29
  class HttpAttachment(BaseModel):
21
30
  """HTTP attachment format for API requests."""
22
- kind: Literal['image', 'document', 'file']
31
+
32
+ kind: Literal["image", "document", "file"]
23
33
  mime_type: Optional[str] = None
24
34
  name: Optional[str] = None
25
35
  url: Optional[str] = None
26
36
  data: Optional[str] = None # Base64 encoded data
27
37
  format: Optional[str] = None
28
38
  use_litellm_format: Optional[bool] = None
29
-
30
- @model_validator(mode='after')
31
- def validate_url_or_data_present(self) -> 'HttpAttachment':
39
+
40
+ @model_validator(mode="after")
41
+ def validate_url_or_data_present(self) -> "HttpAttachment":
32
42
  """Validate that at least one of url or data is present."""
33
43
  if self.url is None and self.data is None:
34
44
  raise ValueError("At least one of 'url' or 'data' must be provided")
35
45
  return self
36
-
37
- @field_validator('data')
46
+
47
+ @field_validator("data")
38
48
  @classmethod
39
49
  def validate_base64_data(cls, v: Optional[str]) -> Optional[str]:
40
50
  """Validate that data is proper base64 encoded."""
@@ -48,18 +58,20 @@ class HttpAttachment(BaseModel):
48
58
  except Exception as e:
49
59
  raise ValueError(f"Invalid base64 encoding: {str(e)}")
50
60
  return v
51
-
52
- @model_validator(mode='after')
53
- def validate_mime_type_consistency(self) -> 'HttpAttachment':
61
+
62
+ @model_validator(mode="after")
63
+ def validate_mime_type_consistency(self) -> "HttpAttachment":
54
64
  """Validate that mime_type is consistent with kind."""
55
65
  if self.mime_type is not None and self.kind is not None:
56
- if self.kind == 'image' and not self.mime_type.startswith('image/'):
57
- raise ValueError(f"For kind='image', mime_type must start with 'image/'. Got: {self.mime_type}")
58
-
59
- elif self.kind == 'document' and not (
60
- self.mime_type.startswith('application/') or
61
- self.mime_type.startswith('text/') or
62
- self.mime_type.startswith('document/')
66
+ if self.kind == "image" and not self.mime_type.startswith("image/"):
67
+ raise ValueError(
68
+ f"For kind='image', mime_type must start with 'image/'. Got: {self.mime_type}"
69
+ )
70
+
71
+ elif self.kind == "document" and not (
72
+ self.mime_type.startswith("application/")
73
+ or self.mime_type.startswith("text/")
74
+ or self.mime_type.startswith("document/")
63
75
  ):
64
76
  raise ValueError(
65
77
  f"For kind='document', mime_type must start with 'application/', 'text/', "
@@ -67,99 +79,137 @@ class HttpAttachment(BaseModel):
67
79
  )
68
80
  return self
69
81
 
82
+
70
83
  class HttpMessageContentPart(BaseModel):
71
84
  """HTTP message content part for multi-part messages."""
72
- type: Literal['text', 'image_url', 'file']
85
+
86
+ type: Literal["text", "image_url", "file"]
73
87
  text: Optional[str] = None
74
88
  image_url: Optional[Dict[str, Any]] = None
75
89
  file: Optional[Dict[str, Any]] = None
76
-
77
- @model_validator(mode='after')
78
- def validate_content_consistency(self) -> 'HttpMessageContentPart':
90
+
91
+ @model_validator(mode="after")
92
+ def validate_content_consistency(self) -> "HttpMessageContentPart":
79
93
  """Validate that exactly one field is populated and it matches the declared type."""
80
94
  # Count non-None content fields
81
95
  populated_fields = []
82
96
  if self.text is not None:
83
- populated_fields.append('text')
97
+ populated_fields.append("text")
84
98
  if self.image_url is not None:
85
- populated_fields.append('image_url')
99
+ populated_fields.append("image_url")
86
100
  if self.file is not None:
87
- populated_fields.append('file')
88
-
101
+ populated_fields.append("file")
102
+
89
103
  # Check if exactly one field is populated
90
104
  if len(populated_fields) != 1:
91
- raise ValueError(f"Exactly one content field must be populated. Found {len(populated_fields)}: {populated_fields}")
92
-
105
+ raise ValueError(
106
+ f"Exactly one content field must be populated. Found {len(populated_fields)}: {populated_fields}"
107
+ )
108
+
93
109
  # Check that the populated field matches the declared type
94
110
  populated_field = populated_fields[0]
95
- if self.type == 'text' and populated_field != 'text':
96
- raise ValueError(f"For type='text', the 'text' field must be populated, but found '{populated_field}' instead")
97
- elif self.type == 'image_url' and populated_field != 'image_url':
98
- raise ValueError(f"For type='image_url', the 'image_url' field must be populated, but found '{populated_field}' instead")
99
- elif self.type == 'file' and populated_field != 'file':
100
- raise ValueError(f"For type='file', the 'file' field must be populated, but found '{populated_field}' instead")
101
-
111
+ if self.type == "text" and populated_field != "text":
112
+ raise ValueError(
113
+ f"For type='text', the 'text' field must be populated, but found '{populated_field}' instead"
114
+ )
115
+ elif self.type == "image_url" and populated_field != "image_url":
116
+ raise ValueError(
117
+ f"For type='image_url', the 'image_url' field must be populated, but found '{populated_field}' instead"
118
+ )
119
+ elif self.type == "file" and populated_field != "file":
120
+ raise ValueError(
121
+ f"For type='file', the 'file' field must be populated, but found '{populated_field}' instead"
122
+ )
123
+
102
124
  return self
103
125
 
126
+
104
127
  # HTTP Message types
105
128
  class HttpMessage(BaseModel):
106
129
  """HTTP message format for API requests."""
107
- role: Literal['user', 'assistant', 'system', 'tool']
130
+
131
+ role: Literal["user", "assistant", "system", "tool"]
108
132
  content: Union[str, List[HttpMessageContentPart]]
109
133
  attachments: Optional[List[HttpAttachment]] = None
110
134
  tool_call_id: Optional[str] = None
111
135
  tool_calls: Optional[List[Dict[str, Any]]] = None
112
136
 
137
+
113
138
  # Approval types for HITL
114
139
  class ApprovalMessage(BaseModel):
115
140
  """Approval message for tool execution."""
116
- type: Literal['approval'] = 'approval'
141
+
142
+ type: Literal["approval"] = "approval"
117
143
  session_id: str = Field(..., description="Session ID for the approval")
118
144
  tool_call_id: str = Field(..., description="ID of the tool call being approved")
119
145
  approved: bool = Field(..., description="Whether the tool execution is approved")
120
- additional_context: Optional[Dict[str, Any]] = Field(default=None, description="Additional context for the approval")
146
+ additional_context: Optional[Dict[str, Any]] = Field(
147
+ default=None, description="Additional context for the approval"
148
+ )
149
+
121
150
 
122
151
  # Request types
123
152
  class ChatRequest(BaseModel):
124
153
  """Request format for chat endpoints."""
154
+
125
155
  messages: List[HttpMessage]
126
156
  agent_name: str = Field(..., description="Name of the agent to use")
127
- context: Optional[Dict[str, Any]] = Field(default_factory=dict, description="Context data for the agent")
157
+ context: Optional[Dict[str, Any]] = Field(
158
+ default_factory=dict, description="Context data for the agent"
159
+ )
128
160
  max_turns: Optional[int] = Field(default=10, description="Maximum number of turns")
129
161
  stream: bool = Field(default=False, description="Whether to stream the response")
130
- conversation_id: Optional[str] = Field(default=None, description="Conversation ID for memory persistence")
131
- memory: Optional[Dict[str, Any]] = Field(default=None, description="Memory configuration override")
132
- store_on_completion: Optional[bool] = Field(default=None, description="Whether to store conversation on completion")
133
- approvals: Optional[List[ApprovalMessage]] = Field(default=None, description="Approval decisions for tool calls")
162
+ conversation_id: Optional[str] = Field(
163
+ default=None, description="Conversation ID for memory persistence"
164
+ )
165
+ memory: Optional[Dict[str, Any]] = Field(
166
+ default=None, description="Memory configuration override"
167
+ )
168
+ store_on_completion: Optional[bool] = Field(
169
+ default=None, description="Whether to store conversation on completion"
170
+ )
171
+ approvals: Optional[List[ApprovalMessage]] = Field(
172
+ default=None, description="Approval decisions for tool calls"
173
+ )
174
+
134
175
 
135
176
  # Interruption types for HITL
136
177
  class ToolCallInterruption(BaseModel):
137
178
  """Tool call interruption data."""
179
+
138
180
  id: str
139
- type: Literal['function'] = 'function'
181
+ type: Literal["function"] = "function"
140
182
  function: Dict[str, str] # name and arguments
141
183
 
184
+
142
185
  class InterruptionData(BaseModel):
143
186
  """Interruption information."""
144
- type: Literal['tool_approval'] = 'tool_approval'
187
+
188
+ type: Literal["tool_approval"] = "tool_approval"
145
189
  tool_call: Optional[ToolCallInterruption]
146
190
  session_id: str
147
191
 
192
+
148
193
  # Base outcome types
149
194
  class BaseOutcomeData(BaseModel):
150
195
  """Base outcome data."""
151
- status: Literal['completed', 'error', 'max_turns', 'interrupted']
196
+
197
+ status: Literal["completed", "error", "max_turns", "interrupted"]
152
198
  output: Optional[str] = None
153
199
  error: Optional[Any] = None
154
200
 
201
+
155
202
  class InterruptedOutcomeData(BaseOutcomeData):
156
203
  """Outcome data for interrupted runs."""
157
- status: Literal['interrupted'] = 'interrupted'
204
+
205
+ status: Literal["interrupted"] = "interrupted"
158
206
  interruptions: Optional[List[InterruptionData]] = None
159
207
 
208
+
160
209
  # Response types
161
210
  class CompletedChatData(BaseModel):
162
211
  """Data for successful chat completion."""
212
+
163
213
  run_id: str
164
214
  trace_id: str
165
215
  messages: List[HttpMessage]
@@ -168,115 +218,153 @@ class CompletedChatData(BaseModel):
168
218
  execution_time_ms: int
169
219
  conversation_id: Optional[str] = None
170
220
 
221
+
171
222
  class ChatResponse(BaseModel):
172
223
  """Response format for chat endpoints."""
224
+
173
225
  success: bool
174
226
  data: Optional[CompletedChatData] = None
175
227
  error: Optional[str] = None
176
228
 
229
+
177
230
  class AgentInfo(BaseModel):
178
231
  """Information about an available agent."""
232
+
179
233
  name: str
180
234
  description: str
181
235
  tools: List[str]
182
236
 
237
+
183
238
  class AgentListData(BaseModel):
184
239
  """Data for agent list response."""
240
+
185
241
  agents: List[AgentInfo]
186
242
 
243
+
187
244
  class AgentListResponse(BaseModel):
188
245
  """Response format for agent list endpoint."""
246
+
189
247
  success: bool
190
248
  data: Optional[AgentListData] = None
191
249
  error: Optional[str] = None
192
250
 
251
+
193
252
  class HealthResponse(BaseModel):
194
253
  """Response format for health check endpoint."""
195
- status: Literal['healthy', 'unhealthy']
254
+
255
+ status: Literal["healthy", "unhealthy"]
196
256
  timestamp: str
197
257
  version: str
198
258
  uptime: int # milliseconds
199
259
 
260
+
200
261
  # Memory-specific response types
201
262
  class ConversationData(BaseModel):
202
263
  """Data for a conversation."""
264
+
203
265
  conversation_id: str
204
266
  user_id: Optional[str] = None
205
267
  messages: List[Dict[str, Any]]
206
268
  metadata: Optional[Dict[str, Any]] = None
207
269
 
270
+
208
271
  class ConversationResponse(BaseModel):
209
272
  """Response format for conversation endpoints."""
273
+
210
274
  success: bool
211
275
  data: Optional[ConversationData] = None
212
276
  error: Optional[str] = None
213
277
 
278
+
214
279
  class MemoryHealthData(BaseModel):
215
280
  """Data for memory health check."""
281
+
216
282
  healthy: bool
217
283
  provider: str
218
284
  latency_ms: float
219
285
  details: Optional[Dict[str, Any]] = None
220
286
 
287
+
221
288
  class MemoryHealthResponse(BaseModel):
222
289
  """Response format for memory health endpoint."""
290
+
223
291
  success: bool
224
292
  data: Optional[MemoryHealthData] = None
225
293
  error: Optional[str] = None
226
294
 
295
+
227
296
  class DeleteConversationData(BaseModel):
228
297
  """Data for delete conversation response."""
298
+
229
299
  conversation_id: str
230
300
  deleted: bool
231
301
 
302
+
232
303
  class DeleteConversationResponse(BaseModel):
233
304
  """Response format for delete conversation endpoint."""
305
+
234
306
  success: bool
235
307
  data: Optional[DeleteConversationData] = None
236
308
  error: Optional[str] = None
237
309
 
310
+
238
311
  # Server configuration
239
312
  @dataclass
240
313
  class ServerConfig(Generic[Ctx]):
241
314
  """Configuration for the JAF HTTP server."""
315
+
242
316
  agent_registry: Dict[str, Agent[Ctx, Any]]
243
317
  run_config: RunConfig[Ctx]
244
- host: str = '127.0.0.1'
318
+ host: str = "127.0.0.1"
245
319
  port: int = 3000
246
320
  cors: Union[bool, Dict[str, Any]] = True
247
321
  default_memory_provider: Optional[MemoryProvider] = None
248
322
 
323
+
249
324
  # Approval response types
250
325
  class PendingApprovalData(BaseModel):
251
326
  """Data for a pending approval."""
327
+
252
328
  conversation_id: str
253
329
  tool_call_id: str
254
330
  tool_name: str
255
331
  args: Dict[str, Any]
256
332
  signature: Optional[str] = None
257
- status: Literal['pending'] = 'pending'
333
+ status: Literal["pending"] = "pending"
258
334
  session_id: Optional[str] = None
259
335
 
336
+
260
337
  class PendingApprovalsData(BaseModel):
261
338
  """Data for pending approvals response."""
339
+
262
340
  pending: List[PendingApprovalData]
263
341
 
342
+
264
343
  class PendingApprovalsResponse(BaseModel):
265
344
  """Response format for pending approvals endpoint."""
345
+
266
346
  success: bool
267
347
  data: Optional[PendingApprovalsData] = None
268
348
  error: Optional[str] = None
269
349
 
350
+
270
351
  # Regeneration types
271
352
  class RegenerationHttpRequest(BaseModel):
272
353
  """HTTP request format for conversation regeneration."""
354
+
273
355
  message_id: str = Field(..., description="ID of the message to regenerate from")
274
356
  agent_name: str = Field(..., description="Name of the agent to use for regeneration")
275
- context: Optional[Dict[str, Any]] = Field(default=None, description="Optional context override for regeneration")
276
- max_turns: Optional[int] = Field(default=10, description="Maximum number of turns for regeneration")
357
+ context: Optional[Dict[str, Any]] = Field(
358
+ default=None, description="Optional context override for regeneration"
359
+ )
360
+ max_turns: Optional[int] = Field(
361
+ default=10, description="Maximum number of turns for regeneration"
362
+ )
363
+
277
364
 
278
365
  class RegenerationData(BaseModel):
279
366
  """Data for successful regeneration response."""
367
+
280
368
  regeneration_id: str
281
369
  conversation_id: str
282
370
  original_message_count: int
@@ -287,36 +375,104 @@ class RegenerationData(BaseModel):
287
375
  turn_count: int
288
376
  execution_time_ms: int
289
377
 
378
+
290
379
  class RegenerationResponse(BaseModel):
291
380
  """Response format for regeneration endpoints."""
381
+
292
382
  success: bool
293
383
  data: Optional[RegenerationData] = None
294
384
  error: Optional[str] = None
295
385
 
386
+
296
387
  class RegenerationPointData(BaseModel):
297
388
  """Data for a regeneration point."""
389
+
298
390
  regeneration_id: str
299
391
  message_id: str
300
392
  timestamp: int
301
393
  original_message_count: int
302
394
  truncated_at_index: int
303
395
 
396
+
304
397
  class RegenerationHistoryData(BaseModel):
305
398
  """Data for regeneration history response."""
399
+
306
400
  conversation_id: str
307
401
  regeneration_points: List[RegenerationPointData]
308
402
 
403
+
309
404
  class RegenerationHistoryResponse(BaseModel):
310
405
  """Response format for regeneration history endpoint."""
406
+
311
407
  success: bool
312
408
  data: Optional[RegenerationHistoryData] = None
313
409
  error: Optional[str] = None
314
410
 
411
+
412
+ # Checkpoint types
413
+ class CheckpointHttpRequest(BaseModel):
414
+ """HTTP request format for conversation checkpoint."""
415
+
416
+ message_id: str = Field(
417
+ ..., description="ID of the message to checkpoint after (this message is kept)"
418
+ )
419
+ context: Optional[Dict[str, Any]] = Field(
420
+ default=None, description="Optional context for the checkpoint"
421
+ )
422
+
423
+
424
+ class CheckpointData(BaseModel):
425
+ """Data for successful checkpoint response."""
426
+
427
+ checkpoint_id: str
428
+ conversation_id: str
429
+ original_message_count: int
430
+ checkpointed_at_index: int
431
+ checkpointed_message_id: str
432
+ messages: List[HttpMessage]
433
+ execution_time_ms: int
434
+
435
+
436
+ class CheckpointResponse(BaseModel):
437
+ """Response format for checkpoint endpoints."""
438
+
439
+ success: bool
440
+ data: Optional[CheckpointData] = None
441
+ error: Optional[str] = None
442
+
443
+
444
+ class CheckpointPointData(BaseModel):
445
+ """Data for a checkpoint point."""
446
+
447
+ checkpoint_id: str
448
+ checkpoint_point: str
449
+ timestamp: int
450
+ original_message_count: int
451
+ checkpointed_at_index: int
452
+ checkpointed_messages: int
453
+
454
+
455
+ class CheckpointHistoryData(BaseModel):
456
+ """Data for checkpoint history response."""
457
+
458
+ conversation_id: str
459
+ checkpoint_points: List[CheckpointPointData]
460
+
461
+
462
+ class CheckpointHistoryResponse(BaseModel):
463
+ """Response format for checkpoint history endpoint."""
464
+
465
+ success: bool
466
+ data: Optional[CheckpointHistoryData] = None
467
+ error: Optional[str] = None
468
+
469
+
315
470
  # Validation schemas
316
471
  def validate_chat_request(data: Dict[str, Any]) -> ChatRequest:
317
472
  """Validate and parse a chat request."""
318
473
  return ChatRequest.model_validate(data)
319
474
 
475
+
320
476
  def validate_regeneration_request(data: Dict[str, Any]) -> RegenerationHttpRequest:
321
477
  """Validate and parse a regeneration request."""
322
478
  return RegenerationHttpRequest.model_validate(data)
jaf/utils/__init__.py CHANGED
@@ -16,7 +16,7 @@ from .attachments import (
16
16
  ATTACHMENT_LIMITS,
17
17
  )
18
18
 
19
- # Import document processing utilities
19
+ # Import document processing utilities
20
20
  from .document_processor import (
21
21
  extract_document_content,
22
22
  is_document_supported,
@@ -30,21 +30,20 @@ from .document_processor import (
30
30
 
31
31
  __all__ = [
32
32
  # Attachment utilities
33
- 'make_image_attachment',
34
- 'make_file_attachment',
35
- 'make_document_attachment',
36
- 'validate_attachment',
37
- 'assert_non_empty_attachment',
38
- 'AttachmentValidationError',
39
- 'ATTACHMENT_LIMITS',
40
-
33
+ "make_image_attachment",
34
+ "make_file_attachment",
35
+ "make_document_attachment",
36
+ "validate_attachment",
37
+ "assert_non_empty_attachment",
38
+ "AttachmentValidationError",
39
+ "ATTACHMENT_LIMITS",
41
40
  # Document processing
42
- 'extract_document_content',
43
- 'is_document_supported',
44
- 'get_document_description',
45
- 'get_missing_dependencies',
46
- 'check_dependencies',
47
- 'ProcessedDocument',
48
- 'DocumentProcessingError',
49
- 'NetworkError',
50
- ]
41
+ "extract_document_content",
42
+ "is_document_supported",
43
+ "get_document_description",
44
+ "get_missing_dependencies",
45
+ "check_dependencies",
46
+ "ProcessedDocument",
47
+ "DocumentProcessingError",
48
+ "NetworkError",
49
+ ]