agnt5 0.2.2__cp39-abi3-macosx_11_0_arm64.whl → 0.2.4__cp39-abi3-macosx_11_0_arm64.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.
Potentially problematic release.
This version of agnt5 might be problematic. Click here for more details.
- agnt5/__init__.py +12 -12
- agnt5/_core.abi3.so +0 -0
- agnt5/_retry_utils.py +169 -0
- agnt5/_schema_utils.py +312 -0
- agnt5/_telemetry.py +28 -7
- agnt5/agent.py +153 -140
- agnt5/client.py +50 -12
- agnt5/context.py +36 -756
- agnt5/entity.py +368 -1160
- agnt5/function.py +208 -235
- agnt5/lm.py +71 -12
- agnt5/tool.py +25 -11
- agnt5/tracing.py +196 -0
- agnt5/worker.py +205 -173
- agnt5/workflow.py +444 -20
- {agnt5-0.2.2.dist-info → agnt5-0.2.4.dist-info}/METADATA +2 -1
- agnt5-0.2.4.dist-info/RECORD +22 -0
- agnt5-0.2.2.dist-info/RECORD +0 -19
- {agnt5-0.2.2.dist-info → agnt5-0.2.4.dist-info}/WHEEL +0 -0
agnt5/workflow.py
CHANGED
|
@@ -5,26 +5,438 @@ from __future__ import annotations
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import functools
|
|
7
7
|
import inspect
|
|
8
|
+
import logging
|
|
8
9
|
import uuid
|
|
9
10
|
from typing import Any, Callable, Dict, Optional, TypeVar, cast
|
|
10
11
|
|
|
12
|
+
from ._schema_utils import extract_function_metadata, extract_function_schemas
|
|
11
13
|
from .context import Context
|
|
14
|
+
from .entity import Entity, EntityState, _get_state_manager
|
|
15
|
+
from .function import FunctionContext
|
|
12
16
|
from .types import HandlerFunc, WorkflowConfig
|
|
13
|
-
from .
|
|
17
|
+
from ._telemetry import setup_module_logger
|
|
18
|
+
|
|
19
|
+
logger = setup_module_logger(__name__)
|
|
14
20
|
|
|
15
21
|
T = TypeVar("T")
|
|
16
22
|
|
|
17
23
|
# Global workflow registry
|
|
18
24
|
_WORKFLOW_REGISTRY: Dict[str, WorkflowConfig] = {}
|
|
19
25
|
|
|
26
|
+
class WorkflowContext(Context):
|
|
27
|
+
"""
|
|
28
|
+
Context for durable workflows.
|
|
29
|
+
|
|
30
|
+
Extends base Context with:
|
|
31
|
+
- State management via WorkflowEntity.state
|
|
32
|
+
- Step tracking and replay
|
|
33
|
+
- Orchestration (task, parallel, gather)
|
|
34
|
+
- Checkpointing (step)
|
|
35
|
+
|
|
36
|
+
WorkflowContext delegates state to the underlying WorkflowEntity,
|
|
37
|
+
which provides durability and state change tracking for AI workflows.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def __init__(
|
|
41
|
+
self,
|
|
42
|
+
workflow_entity: "WorkflowEntity", # Forward reference
|
|
43
|
+
run_id: str,
|
|
44
|
+
attempt: int = 0,
|
|
45
|
+
runtime_context: Optional[Any] = None,
|
|
46
|
+
) -> None:
|
|
47
|
+
"""
|
|
48
|
+
Initialize workflow context.
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
workflow_entity: WorkflowEntity instance managing workflow state
|
|
52
|
+
run_id: Unique workflow run identifier
|
|
53
|
+
attempt: Retry attempt number (0-indexed)
|
|
54
|
+
runtime_context: RuntimeContext for trace correlation
|
|
55
|
+
"""
|
|
56
|
+
super().__init__(run_id, attempt, runtime_context)
|
|
57
|
+
self._workflow_entity = workflow_entity
|
|
58
|
+
self._step_counter: int = 0 # Track step sequence
|
|
59
|
+
|
|
60
|
+
# === State Management ===
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def state(self):
|
|
64
|
+
"""
|
|
65
|
+
Delegate to WorkflowEntity.state for durable state management.
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
WorkflowState instance from the workflow entity
|
|
69
|
+
|
|
70
|
+
Example:
|
|
71
|
+
ctx.state.set("status", "processing")
|
|
72
|
+
status = ctx.state.get("status")
|
|
73
|
+
"""
|
|
74
|
+
return self._workflow_entity.state
|
|
75
|
+
|
|
76
|
+
# === Orchestration ===
|
|
77
|
+
|
|
78
|
+
async def task(
|
|
79
|
+
self,
|
|
80
|
+
handler: Union[str, Callable],
|
|
81
|
+
*args: Any,
|
|
82
|
+
**kwargs: Any,
|
|
83
|
+
) -> Any:
|
|
84
|
+
"""
|
|
85
|
+
Execute a function and wait for result.
|
|
86
|
+
|
|
87
|
+
Supports two calling patterns:
|
|
88
|
+
|
|
89
|
+
1. **Type-safe with function reference (recommended)**:
|
|
90
|
+
```python
|
|
91
|
+
result = await ctx.task(process_data, arg1, arg2, kwarg=value)
|
|
92
|
+
```
|
|
93
|
+
Full IDE support, type checking, and refactoring safety.
|
|
94
|
+
|
|
95
|
+
2. **Legacy string-based (backward compatible)**:
|
|
96
|
+
```python
|
|
97
|
+
result = await ctx.task("function_name", input=data)
|
|
98
|
+
```
|
|
99
|
+
String lookup without type safety.
|
|
100
|
+
|
|
101
|
+
Args:
|
|
102
|
+
handler: Either a @function reference (recommended) or string name (legacy)
|
|
103
|
+
*args: Positional arguments to pass to the function
|
|
104
|
+
**kwargs: Keyword arguments to pass to the function
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Function result
|
|
108
|
+
|
|
109
|
+
Example (type-safe):
|
|
110
|
+
```python
|
|
111
|
+
@function
|
|
112
|
+
async def process_data(ctx: FunctionContext, data: list, multiplier: int = 2):
|
|
113
|
+
return [x * multiplier for x in data]
|
|
114
|
+
|
|
115
|
+
@workflow
|
|
116
|
+
async def my_workflow(ctx: WorkflowContext):
|
|
117
|
+
# Type-safe call with positional and keyword args
|
|
118
|
+
result = await ctx.task(process_data, [1, 2, 3], multiplier=3)
|
|
119
|
+
return result
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Example (legacy):
|
|
123
|
+
```python
|
|
124
|
+
result = await ctx.task("process_data", input={"data": [1, 2, 3]})
|
|
125
|
+
```
|
|
126
|
+
"""
|
|
127
|
+
from .function import FunctionRegistry
|
|
128
|
+
|
|
129
|
+
# Extract handler name from function reference or use string
|
|
130
|
+
if callable(handler):
|
|
131
|
+
handler_name = handler.__name__
|
|
132
|
+
if not hasattr(handler, '_agnt5_config'):
|
|
133
|
+
raise ValueError(
|
|
134
|
+
f"Function '{handler_name}' is not a registered @function. "
|
|
135
|
+
f"Did you forget to add the @function decorator?"
|
|
136
|
+
)
|
|
137
|
+
else:
|
|
138
|
+
handler_name = handler
|
|
139
|
+
|
|
140
|
+
# Generate unique step name for durability
|
|
141
|
+
step_name = f"{handler_name}_{self._step_counter}"
|
|
142
|
+
self._step_counter += 1
|
|
143
|
+
|
|
144
|
+
# Check if step already completed (for replay)
|
|
145
|
+
if self._workflow_entity.has_completed_step(step_name):
|
|
146
|
+
result = self._workflow_entity.get_completed_step(step_name)
|
|
147
|
+
self._logger.info(f"🔄 Replaying cached step: {step_name}")
|
|
148
|
+
return result
|
|
149
|
+
|
|
150
|
+
# Execute function
|
|
151
|
+
self._logger.info(f"▶️ Executing new step: {step_name}")
|
|
152
|
+
func_config = FunctionRegistry.get(handler_name)
|
|
153
|
+
if func_config is None:
|
|
154
|
+
raise ValueError(f"Function '{handler_name}' not found in registry")
|
|
155
|
+
|
|
156
|
+
# Create FunctionContext for the function execution
|
|
157
|
+
func_ctx = FunctionContext(
|
|
158
|
+
run_id=f"{self.run_id}:task:{handler_name}",
|
|
159
|
+
runtime_context=self._runtime_context,
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
# Execute function with arguments
|
|
163
|
+
# Support legacy pattern: ctx.task("func_name", input=data) or ctx.task(func_ref, input=data)
|
|
164
|
+
if len(args) == 0 and "input" in kwargs:
|
|
165
|
+
# Legacy pattern - single input parameter
|
|
166
|
+
input_data = kwargs.pop("input") # Remove from kwargs
|
|
167
|
+
result = await func_config.handler(func_ctx, input_data, **kwargs)
|
|
168
|
+
else:
|
|
169
|
+
# Type-safe pattern - pass all args/kwargs
|
|
170
|
+
result = await func_config.handler(func_ctx, *args, **kwargs)
|
|
171
|
+
|
|
172
|
+
# Record step completion in WorkflowEntity
|
|
173
|
+
self._workflow_entity.record_step_completion(step_name, handler_name, args or kwargs, result)
|
|
174
|
+
|
|
175
|
+
return result
|
|
176
|
+
|
|
177
|
+
async def parallel(self, *tasks: Awaitable[T]) -> List[T]:
|
|
178
|
+
"""
|
|
179
|
+
Run multiple tasks in parallel.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
*tasks: Async tasks to run in parallel
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
List of results in the same order as tasks
|
|
186
|
+
|
|
187
|
+
Example:
|
|
188
|
+
result1, result2 = await ctx.parallel(
|
|
189
|
+
fetch_data(source1),
|
|
190
|
+
fetch_data(source2)
|
|
191
|
+
)
|
|
192
|
+
"""
|
|
193
|
+
import asyncio
|
|
194
|
+
return list(await asyncio.gather(*tasks))
|
|
195
|
+
|
|
196
|
+
async def gather(self, **tasks: Awaitable[T]) -> Dict[str, T]:
|
|
197
|
+
"""
|
|
198
|
+
Run tasks in parallel with named results.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
**tasks: Named async tasks to run in parallel
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Dictionary mapping names to results
|
|
205
|
+
|
|
206
|
+
Example:
|
|
207
|
+
results = await ctx.gather(
|
|
208
|
+
db=query_database(),
|
|
209
|
+
api=fetch_api()
|
|
210
|
+
)
|
|
211
|
+
"""
|
|
212
|
+
import asyncio
|
|
213
|
+
keys = list(tasks.keys())
|
|
214
|
+
values = list(tasks.values())
|
|
215
|
+
results = await asyncio.gather(*values)
|
|
216
|
+
return dict(zip(keys, results))
|
|
217
|
+
|
|
218
|
+
async def step(
|
|
219
|
+
self,
|
|
220
|
+
name: str,
|
|
221
|
+
func_or_awaitable: Union[Callable[[], Awaitable[T]], Awaitable[T]]
|
|
222
|
+
) -> T:
|
|
223
|
+
"""
|
|
224
|
+
Checkpoint expensive operations for durability.
|
|
225
|
+
|
|
226
|
+
If workflow crashes, won't re-execute this step on retry.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
name: Unique name for this checkpoint
|
|
230
|
+
func_or_awaitable: Either an async function or awaitable
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
The result of the function/awaitable
|
|
234
|
+
|
|
235
|
+
Example:
|
|
236
|
+
result = await ctx.step("load", load_data())
|
|
237
|
+
"""
|
|
238
|
+
import inspect
|
|
239
|
+
|
|
240
|
+
# Check if step already completed (for replay)
|
|
241
|
+
if self._workflow_entity.has_completed_step(name):
|
|
242
|
+
result = self._workflow_entity.get_completed_step(name)
|
|
243
|
+
self._logger.info(f"🔄 Replaying checkpoint: {name}")
|
|
244
|
+
return result
|
|
245
|
+
|
|
246
|
+
# Execute and checkpoint
|
|
247
|
+
if inspect.iscoroutine(func_or_awaitable) or inspect.isawaitable(func_or_awaitable):
|
|
248
|
+
result = await func_or_awaitable
|
|
249
|
+
else:
|
|
250
|
+
result = await func_or_awaitable()
|
|
251
|
+
|
|
252
|
+
# Record step completion
|
|
253
|
+
self._workflow_entity.record_step_completion(name, "checkpoint", None, result)
|
|
254
|
+
|
|
255
|
+
return result
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
# ============================================================================
|
|
259
|
+
# WorkflowEntity: Entity specialized for workflow execution state
|
|
260
|
+
# ============================================================================
|
|
261
|
+
|
|
262
|
+
class WorkflowEntity(Entity):
|
|
263
|
+
"""
|
|
264
|
+
Entity specialized for workflow execution state.
|
|
265
|
+
|
|
266
|
+
Extends Entity with workflow-specific capabilities:
|
|
267
|
+
- Step tracking for replay and crash recovery
|
|
268
|
+
- State change tracking for debugging and audit (AI workflows)
|
|
269
|
+
- Completed step cache for efficient replay
|
|
270
|
+
|
|
271
|
+
Workflows are temporary entities - they exist for the duration of
|
|
272
|
+
execution and their state is used for coordination between steps.
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
def __init__(self, run_id: str):
|
|
276
|
+
"""
|
|
277
|
+
Initialize workflow entity.
|
|
278
|
+
|
|
279
|
+
Args:
|
|
280
|
+
run_id: Unique workflow run identifier
|
|
281
|
+
"""
|
|
282
|
+
# Initialize as entity with workflow key pattern
|
|
283
|
+
super().__init__(key=f"workflow:{run_id}")
|
|
284
|
+
|
|
285
|
+
# Step tracking for replay and recovery
|
|
286
|
+
self._step_events: list[Dict[str, Any]] = []
|
|
287
|
+
self._completed_steps: Dict[str, Any] = {}
|
|
288
|
+
|
|
289
|
+
# State change tracking for debugging/audit (AI workflows)
|
|
290
|
+
self._state_changes: list[Dict[str, Any]] = []
|
|
291
|
+
|
|
292
|
+
logger.debug(f"Created WorkflowEntity: {run_id}")
|
|
293
|
+
|
|
294
|
+
@property
|
|
295
|
+
def run_id(self) -> str:
|
|
296
|
+
"""Extract run_id from workflow key."""
|
|
297
|
+
return self._key.split(":", 1)[1]
|
|
298
|
+
|
|
299
|
+
def record_step_completion(
|
|
300
|
+
self,
|
|
301
|
+
step_name: str,
|
|
302
|
+
handler_name: str,
|
|
303
|
+
input_data: Any,
|
|
304
|
+
result: Any
|
|
305
|
+
) -> None:
|
|
306
|
+
"""
|
|
307
|
+
Record completed step for replay and recovery.
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
step_name: Unique step identifier
|
|
311
|
+
handler_name: Function handler name
|
|
312
|
+
input_data: Input data passed to function
|
|
313
|
+
result: Function result
|
|
314
|
+
"""
|
|
315
|
+
self._step_events.append({
|
|
316
|
+
"step_name": step_name,
|
|
317
|
+
"handler_name": handler_name,
|
|
318
|
+
"input": input_data,
|
|
319
|
+
"result": result
|
|
320
|
+
})
|
|
321
|
+
self._completed_steps[step_name] = result
|
|
322
|
+
logger.debug(f"Recorded step completion: {step_name}")
|
|
323
|
+
|
|
324
|
+
def get_completed_step(self, step_name: str) -> Optional[Any]:
|
|
325
|
+
"""
|
|
326
|
+
Get result of completed step (for replay).
|
|
327
|
+
|
|
328
|
+
Args:
|
|
329
|
+
step_name: Step identifier
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
Step result if completed, None otherwise
|
|
333
|
+
"""
|
|
334
|
+
return self._completed_steps.get(step_name)
|
|
335
|
+
|
|
336
|
+
def has_completed_step(self, step_name: str) -> bool:
|
|
337
|
+
"""Check if step has been completed."""
|
|
338
|
+
return step_name in self._completed_steps
|
|
339
|
+
|
|
340
|
+
@property
|
|
341
|
+
def state(self) -> "WorkflowState":
|
|
342
|
+
"""
|
|
343
|
+
Get workflow state with change tracking.
|
|
344
|
+
|
|
345
|
+
Returns WorkflowState which tracks all state mutations
|
|
346
|
+
for debugging and replay of AI workflows.
|
|
347
|
+
"""
|
|
348
|
+
if self._state is None:
|
|
349
|
+
# Get state dict from state manager
|
|
350
|
+
state_manager = _get_state_manager()
|
|
351
|
+
state_dict = state_manager.get_or_create_state(self._state_key)
|
|
352
|
+
self._state = WorkflowState(state_dict, self)
|
|
353
|
+
return self._state
|
|
354
|
+
|
|
355
|
+
|
|
356
|
+
class WorkflowState(EntityState):
|
|
357
|
+
"""
|
|
358
|
+
State interface for WorkflowEntity with change tracking.
|
|
359
|
+
|
|
360
|
+
Extends EntityState to track all state mutations for:
|
|
361
|
+
- AI workflow debugging
|
|
362
|
+
- Audit trail
|
|
363
|
+
- Replay capabilities
|
|
364
|
+
"""
|
|
365
|
+
|
|
366
|
+
def __init__(self, state_dict: Dict[str, Any], workflow_entity: WorkflowEntity):
|
|
367
|
+
"""
|
|
368
|
+
Initialize workflow state.
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
state_dict: Dictionary to use for state storage
|
|
372
|
+
workflow_entity: Parent workflow entity for tracking
|
|
373
|
+
"""
|
|
374
|
+
super().__init__(state_dict)
|
|
375
|
+
self._workflow_entity = workflow_entity
|
|
376
|
+
|
|
377
|
+
def set(self, key: str, value: Any) -> None:
|
|
378
|
+
"""Set value and track change."""
|
|
379
|
+
super().set(key, value)
|
|
380
|
+
# Track change for debugging/audit
|
|
381
|
+
import time
|
|
382
|
+
self._workflow_entity._state_changes.append({
|
|
383
|
+
"key": key,
|
|
384
|
+
"value": value,
|
|
385
|
+
"timestamp": time.time(),
|
|
386
|
+
"deleted": False
|
|
387
|
+
})
|
|
388
|
+
|
|
389
|
+
def delete(self, key: str) -> None:
|
|
390
|
+
"""Delete key and track change."""
|
|
391
|
+
super().delete(key)
|
|
392
|
+
# Track deletion
|
|
393
|
+
import time
|
|
394
|
+
self._workflow_entity._state_changes.append({
|
|
395
|
+
"key": key,
|
|
396
|
+
"value": None,
|
|
397
|
+
"timestamp": time.time(),
|
|
398
|
+
"deleted": True
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
def clear(self) -> None:
|
|
402
|
+
"""Clear all state and track change."""
|
|
403
|
+
super().clear()
|
|
404
|
+
# Track clear operation
|
|
405
|
+
import time
|
|
406
|
+
self._workflow_entity._state_changes.append({
|
|
407
|
+
"key": "__clear__",
|
|
408
|
+
"value": None,
|
|
409
|
+
"timestamp": time.time(),
|
|
410
|
+
"deleted": True
|
|
411
|
+
})
|
|
412
|
+
|
|
20
413
|
|
|
21
414
|
class WorkflowRegistry:
|
|
22
415
|
"""Registry for workflow handlers."""
|
|
23
416
|
|
|
24
417
|
@staticmethod
|
|
25
418
|
def register(config: WorkflowConfig) -> None:
|
|
26
|
-
"""
|
|
419
|
+
"""
|
|
420
|
+
Register a workflow handler.
|
|
421
|
+
|
|
422
|
+
Raises:
|
|
423
|
+
ValueError: If a workflow with this name is already registered
|
|
424
|
+
"""
|
|
425
|
+
if config.name in _WORKFLOW_REGISTRY:
|
|
426
|
+
existing_workflow = _WORKFLOW_REGISTRY[config.name]
|
|
427
|
+
logger.error(
|
|
428
|
+
f"Workflow name collision detected: '{config.name}'\n"
|
|
429
|
+
f" First defined in: {existing_workflow.handler.__module__}\n"
|
|
430
|
+
f" Also defined in: {config.handler.__module__}\n"
|
|
431
|
+
f" This is a bug - workflows must have unique names."
|
|
432
|
+
)
|
|
433
|
+
raise ValueError(
|
|
434
|
+
f"Workflow '{config.name}' is already registered. "
|
|
435
|
+
f"Use @workflow(name='unique_name') to specify a different name."
|
|
436
|
+
)
|
|
437
|
+
|
|
27
438
|
_WORKFLOW_REGISTRY[config.name] = config
|
|
439
|
+
logger.debug(f"Registered workflow '{config.name}'")
|
|
28
440
|
|
|
29
441
|
@staticmethod
|
|
30
442
|
def get(name: str) -> Optional[WorkflowConfig]:
|
|
@@ -55,25 +467,30 @@ def workflow(
|
|
|
55
467
|
"""
|
|
56
468
|
Decorator to mark a function as an AGNT5 durable workflow.
|
|
57
469
|
|
|
58
|
-
|
|
59
|
-
|
|
470
|
+
Workflows use WorkflowEntity for state management and WorkflowContext
|
|
471
|
+
for orchestration. State changes are automatically tracked for replay.
|
|
60
472
|
|
|
61
473
|
Args:
|
|
62
474
|
name: Custom workflow name (default: function's __name__)
|
|
63
475
|
|
|
64
476
|
Example:
|
|
65
477
|
@workflow
|
|
66
|
-
async def process_order(ctx:
|
|
478
|
+
async def process_order(ctx: WorkflowContext, order_id: str) -> dict:
|
|
479
|
+
# Durable state - survives crashes
|
|
480
|
+
ctx.state.set("status", "processing")
|
|
481
|
+
ctx.state.set("order_id", order_id)
|
|
482
|
+
|
|
67
483
|
# Validate order
|
|
68
|
-
order = await ctx.task(
|
|
484
|
+
order = await ctx.task(validate_order, input={"order_id": order_id})
|
|
69
485
|
|
|
70
|
-
# Process payment
|
|
71
|
-
payment = await ctx.
|
|
486
|
+
# Process payment (checkpointed - won't re-execute on crash)
|
|
487
|
+
payment = await ctx.step("payment", process_payment(order["total"]))
|
|
72
488
|
|
|
73
489
|
# Fulfill order
|
|
74
|
-
await ctx.task(
|
|
490
|
+
await ctx.task(ship_order, input={"order_id": order_id})
|
|
75
491
|
|
|
76
|
-
|
|
492
|
+
ctx.state.set("status", "completed")
|
|
493
|
+
return {"status": ctx.state.get("status")}
|
|
77
494
|
"""
|
|
78
495
|
|
|
79
496
|
def decorator(func: Callable[..., Any]) -> Callable[..., Any]:
|
|
@@ -86,7 +503,7 @@ def workflow(
|
|
|
86
503
|
|
|
87
504
|
if not params or params[0].name != "ctx":
|
|
88
505
|
raise ValueError(
|
|
89
|
-
f"Workflow '{workflow_name}' must have 'ctx:
|
|
506
|
+
f"Workflow '{workflow_name}' must have 'ctx: WorkflowContext' as first parameter"
|
|
90
507
|
)
|
|
91
508
|
|
|
92
509
|
# Convert sync to async if needed
|
|
@@ -101,10 +518,10 @@ def workflow(
|
|
|
101
518
|
handler_func = cast(HandlerFunc, async_wrapper)
|
|
102
519
|
|
|
103
520
|
# Extract schemas from type hints
|
|
104
|
-
input_schema, output_schema =
|
|
521
|
+
input_schema, output_schema = extract_function_schemas(func)
|
|
105
522
|
|
|
106
523
|
# Extract metadata (description, etc.)
|
|
107
|
-
metadata =
|
|
524
|
+
metadata = extract_function_metadata(func)
|
|
108
525
|
|
|
109
526
|
# Register workflow
|
|
110
527
|
config = WorkflowConfig(
|
|
@@ -119,17 +536,24 @@ def workflow(
|
|
|
119
536
|
# Create wrapper that provides context
|
|
120
537
|
@functools.wraps(func)
|
|
121
538
|
async def wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
122
|
-
# Create
|
|
123
|
-
if not args or not isinstance(args[0],
|
|
124
|
-
# Auto-create context for direct workflow calls
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
539
|
+
# Create WorkflowEntity and WorkflowContext if not provided
|
|
540
|
+
if not args or not isinstance(args[0], WorkflowContext):
|
|
541
|
+
# Auto-create workflow entity and context for direct workflow calls
|
|
542
|
+
run_id = f"workflow-{uuid.uuid4().hex[:8]}"
|
|
543
|
+
|
|
544
|
+
# Create WorkflowEntity to manage state
|
|
545
|
+
workflow_entity = WorkflowEntity(run_id=run_id)
|
|
546
|
+
|
|
547
|
+
# Create WorkflowContext that wraps the entity
|
|
548
|
+
ctx = WorkflowContext(
|
|
549
|
+
workflow_entity=workflow_entity,
|
|
550
|
+
run_id=run_id,
|
|
128
551
|
)
|
|
552
|
+
|
|
129
553
|
# Execute workflow
|
|
130
554
|
return await handler_func(ctx, *args, **kwargs)
|
|
131
555
|
else:
|
|
132
|
-
#
|
|
556
|
+
# WorkflowContext provided - use it
|
|
133
557
|
return await handler_func(*args, **kwargs)
|
|
134
558
|
|
|
135
559
|
# Store config on wrapper for introspection
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agnt5
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Classifier: Development Status :: 3 - Alpha
|
|
5
5
|
Classifier: Intended Audience :: Developers
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -16,6 +16,7 @@ Requires-Dist: maturin>=1.9.3
|
|
|
16
16
|
Requires-Dist: docstring-parser>=0.15
|
|
17
17
|
Requires-Dist: typing-extensions>=4.8
|
|
18
18
|
Requires-Dist: httpx>=0.28.1
|
|
19
|
+
Requires-Dist: pydantic>=2.0
|
|
19
20
|
Summary: AGNT5 Python SDK - Build durable, resilient agent-first applications
|
|
20
21
|
Author-email: AGNT5 Team <team@agnt5.com>
|
|
21
22
|
License: Apache-2.0
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
agnt5-0.2.4.dist-info/METADATA,sha256=0WFxoXI9jA26vzUKQp2Oqj8ZCb7H50v9PR7pxpXNXgo,994
|
|
2
|
+
agnt5-0.2.4.dist-info/WHEEL,sha256=vpqC0tRn_8bTHidvtrPbrnFQPZnrhuKzsjDdeKwCd58,102
|
|
3
|
+
agnt5/__init__.py,sha256=NAziyM0ZKahdzqAP2edFHTbVHYVdIjEGXm0oEHdmiuo,2011
|
|
4
|
+
agnt5/_compat.py,sha256=BGuy3v5VDOHVa5f3Z-C22iMN19lAt0mPmXwF3qSSWxI,369
|
|
5
|
+
agnt5/_core.abi3.so,sha256=_eS8cS8moUInKso8deBYKQBY56Mwt2gOUw3xCkvzMBs,11889296
|
|
6
|
+
agnt5/_retry_utils.py,sha256=loHsWY5BR4wZy57IzcDEjQAy88DHVwVIr25Cn1d9GPA,5801
|
|
7
|
+
agnt5/_schema_utils.py,sha256=MR67RW757T4Oq2Jqf4kB61H_b51zwaf3CLWELnkngRo,9572
|
|
8
|
+
agnt5/_telemetry.py,sha256=bIY9AvBRjJBTHoBPbfR6X1OgaiUf-T0vCoi0_snsWXA,5957
|
|
9
|
+
agnt5/agent.py,sha256=VQqNSd9o3tezxEo6jpWQyk399Wue7NgCpZvuQ-LtiTI,27494
|
|
10
|
+
agnt5/client.py,sha256=kXksazgxdVXWaG9OkjJA4cWruNtcS-ENhtnkrIdw-Nk,23212
|
|
11
|
+
agnt5/context.py,sha256=S2OzPkhn_jnqSWfT21mSYOux8vHaLKQxcAvggZDHQek,2378
|
|
12
|
+
agnt5/entity.py,sha256=dhdxXUxED79u3OlX9yw-2TLCC9VqBcJqES2kx-fDChs,19041
|
|
13
|
+
agnt5/exceptions.py,sha256=mZ0q-NK6OKhYxgwBJpIbgpgzk-CJaFIHDbp1EE-pS7I,925
|
|
14
|
+
agnt5/function.py,sha256=f1vaAlJRwuo8cxCOGEd8XPido00mOhlPS8UJJx-6hJI,11041
|
|
15
|
+
agnt5/lm.py,sha256=AQ8xz5u1mywBKIGwT3AJKtChVdxo5HOCvzdwZ4Ilbb0,21177
|
|
16
|
+
agnt5/tool.py,sha256=uc4L-Q9QyLzQDe-MZKk2Wo3o5e-mK8tfaQwVDgQdouQ,13133
|
|
17
|
+
agnt5/tracing.py,sha256=Mh2-OfnQM61lM_P8gxJstafdsUA8Gxoo1lP-Joxhub8,5980
|
|
18
|
+
agnt5/types.py,sha256=Zb71ZMwvrt1p4SH18cAKunp2y5tao_W5_jGYaPDejQo,2840
|
|
19
|
+
agnt5/version.py,sha256=rOq1mObLihnnKgKqBrwZA0zwOPudEKVFcW1a48ynkqc,573
|
|
20
|
+
agnt5/worker.py,sha256=_BnqqqvQE16FuezFbPda9FoiKAwcezSaxxNjU0rTjhs,33576
|
|
21
|
+
agnt5/workflow.py,sha256=sve85o4N16PrlIwtQOqJPsHHWgl388SV702IPEXO7A8,18528
|
|
22
|
+
agnt5-0.2.4.dist-info/RECORD,,
|
agnt5-0.2.2.dist-info/RECORD
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
agnt5-0.2.2.dist-info/METADATA,sha256=lGJESH9_HBx_FmDngvJl_cDBCF02SfJsL_mJlWusN1E,965
|
|
2
|
-
agnt5-0.2.2.dist-info/WHEEL,sha256=vpqC0tRn_8bTHidvtrPbrnFQPZnrhuKzsjDdeKwCd58,102
|
|
3
|
-
agnt5/__init__.py,sha256=dQ83SuDrAU83leQ38mDVdthZFP0SWJMK67xq1d4uGtg,1933
|
|
4
|
-
agnt5/_compat.py,sha256=BGuy3v5VDOHVa5f3Z-C22iMN19lAt0mPmXwF3qSSWxI,369
|
|
5
|
-
agnt5/_core.abi3.so,sha256=cWTAlgAIyMzQ4Gmp-Cq-5lnlZ1BTYg4IEWhgxv3WvjA,11895200
|
|
6
|
-
agnt5/_telemetry.py,sha256=ZeABlTMoEnXYnBFVOn6ruaSvfeS7r_czI_Pk_tiCfhY,4899
|
|
7
|
-
agnt5/agent.py,sha256=sU0qSZyXl_D6YrQKxtlyR6gggPJ0zZKC63bWtJvMzJI,26557
|
|
8
|
-
agnt5/client.py,sha256=Zesl-TIie8Gwq-ajTauPlAsF7jZm5TPk307iHdxQ2SM,21214
|
|
9
|
-
agnt5/context.py,sha256=fF_eL_rY-GFnGFpL6wqPERykluGf2obpD-mYrGpsUTc,23817
|
|
10
|
-
agnt5/entity.py,sha256=kLMBlHSQJeRqNwfWcXeLcM5UF9JcU8aET6TQBh6tSBA,43966
|
|
11
|
-
agnt5/exceptions.py,sha256=mZ0q-NK6OKhYxgwBJpIbgpgzk-CJaFIHDbp1EE-pS7I,925
|
|
12
|
-
agnt5/function.py,sha256=hH7rH9c_ZohUuNoLlBJHuiR5qPhw0L0pkrVVrpL_g2M,11273
|
|
13
|
-
agnt5/lm.py,sha256=VvKVm_7VneBggLoCZEXfTavpPJJXNpbARrgHPCqhrX8,18915
|
|
14
|
-
agnt5/tool.py,sha256=5s0YK5-BFIlQNUJOQHbejQsWW17r43rAC4zRvJnh9qg,12513
|
|
15
|
-
agnt5/types.py,sha256=Zb71ZMwvrt1p4SH18cAKunp2y5tao_W5_jGYaPDejQo,2840
|
|
16
|
-
agnt5/version.py,sha256=rOq1mObLihnnKgKqBrwZA0zwOPudEKVFcW1a48ynkqc,573
|
|
17
|
-
agnt5/worker.py,sha256=n4zL4DUocRlcpitsOPbrZ4frz-E_ZoVgIhKH_76iq00,31502
|
|
18
|
-
agnt5/workflow.py,sha256=1sFAvtdZn6n7nDDb0YyE6LGJGuN2KVFNVo9Q42N_7_k,4529
|
|
19
|
-
agnt5-0.2.2.dist-info/RECORD,,
|
|
File without changes
|