agnt5 0.1.0__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.
Files changed (49) hide show
  1. agnt5/__init__.py +307 -0
  2. agnt5/__pycache__/__init__.cpython-311.pyc +0 -0
  3. agnt5/__pycache__/agent.cpython-311.pyc +0 -0
  4. agnt5/__pycache__/context.cpython-311.pyc +0 -0
  5. agnt5/__pycache__/durable.cpython-311.pyc +0 -0
  6. agnt5/__pycache__/extraction.cpython-311.pyc +0 -0
  7. agnt5/__pycache__/memory.cpython-311.pyc +0 -0
  8. agnt5/__pycache__/reflection.cpython-311.pyc +0 -0
  9. agnt5/__pycache__/runtime.cpython-311.pyc +0 -0
  10. agnt5/__pycache__/task.cpython-311.pyc +0 -0
  11. agnt5/__pycache__/tool.cpython-311.pyc +0 -0
  12. agnt5/__pycache__/tracing.cpython-311.pyc +0 -0
  13. agnt5/__pycache__/types.cpython-311.pyc +0 -0
  14. agnt5/__pycache__/workflow.cpython-311.pyc +0 -0
  15. agnt5/_core.abi3.so +0 -0
  16. agnt5/agent.py +1086 -0
  17. agnt5/context.py +406 -0
  18. agnt5/durable.py +1050 -0
  19. agnt5/extraction.py +410 -0
  20. agnt5/llm/__init__.py +179 -0
  21. agnt5/llm/__pycache__/__init__.cpython-311.pyc +0 -0
  22. agnt5/llm/__pycache__/anthropic.cpython-311.pyc +0 -0
  23. agnt5/llm/__pycache__/azure.cpython-311.pyc +0 -0
  24. agnt5/llm/__pycache__/base.cpython-311.pyc +0 -0
  25. agnt5/llm/__pycache__/google.cpython-311.pyc +0 -0
  26. agnt5/llm/__pycache__/mistral.cpython-311.pyc +0 -0
  27. agnt5/llm/__pycache__/openai.cpython-311.pyc +0 -0
  28. agnt5/llm/__pycache__/together.cpython-311.pyc +0 -0
  29. agnt5/llm/anthropic.py +319 -0
  30. agnt5/llm/azure.py +348 -0
  31. agnt5/llm/base.py +315 -0
  32. agnt5/llm/google.py +373 -0
  33. agnt5/llm/mistral.py +330 -0
  34. agnt5/llm/model_registry.py +467 -0
  35. agnt5/llm/models.json +227 -0
  36. agnt5/llm/openai.py +334 -0
  37. agnt5/llm/together.py +377 -0
  38. agnt5/memory.py +746 -0
  39. agnt5/reflection.py +514 -0
  40. agnt5/runtime.py +699 -0
  41. agnt5/task.py +476 -0
  42. agnt5/testing.py +451 -0
  43. agnt5/tool.py +516 -0
  44. agnt5/tracing.py +624 -0
  45. agnt5/types.py +210 -0
  46. agnt5/workflow.py +897 -0
  47. agnt5-0.1.0.dist-info/METADATA +93 -0
  48. agnt5-0.1.0.dist-info/RECORD +49 -0
  49. agnt5-0.1.0.dist-info/WHEEL +4 -0
agnt5/task.py ADDED
@@ -0,0 +1,476 @@
1
+ """
2
+ Task system for AGNT5 SDK.
3
+
4
+ Provides @task decorator for creating structured, reusable operations with
5
+ validation, error handling, and integration with durable execution.
6
+ """
7
+
8
+ import asyncio
9
+ import inspect
10
+ import json
11
+ import logging
12
+ from abc import ABC, abstractmethod
13
+ from dataclasses import dataclass, field
14
+ from typing import Any, Callable, Dict, List, Optional, Type, TypeVar, Union, get_type_hints
15
+ from datetime import datetime
16
+ from enum import Enum
17
+
18
+ from .durable import durable
19
+ from .context import Context, get_context
20
+ from .tracing import trace_agent_run, span, traced, log, TraceLevel
21
+
22
+ logger = logging.getLogger(__name__)
23
+
24
+ T = TypeVar('T')
25
+
26
+
27
+ class TaskStatus(Enum):
28
+ """Task execution status."""
29
+ PENDING = "pending"
30
+ RUNNING = "running"
31
+ COMPLETED = "completed"
32
+ FAILED = "failed"
33
+ CANCELLED = "cancelled"
34
+
35
+
36
+ class OutputFormat(Enum):
37
+ """Supported output formats."""
38
+ JSON = "json"
39
+ PYDANTIC = "pydantic"
40
+ STRING = "string"
41
+ RAW = "raw"
42
+
43
+
44
+ @dataclass
45
+ class TaskParameter:
46
+ """Represents a task parameter with validation."""
47
+ name: str
48
+ type: Type
49
+ description: Optional[str] = None
50
+ required: bool = True
51
+ default: Any = None
52
+
53
+ def to_dict(self) -> Dict[str, Any]:
54
+ return {
55
+ "name": self.name,
56
+ "type": self.type.__name__ if hasattr(self.type, '__name__') else str(self.type),
57
+ "description": self.description,
58
+ "required": self.required,
59
+ "default": self.default
60
+ }
61
+
62
+
63
+ @dataclass
64
+ class TaskConfig:
65
+ """Configuration for a task."""
66
+ name: str
67
+ description: Optional[str] = None
68
+ output_format: OutputFormat = OutputFormat.RAW
69
+ output_schema: Optional[Dict[str, Any]] = None
70
+ retry_count: int = 3
71
+ timeout: Optional[float] = None
72
+ enable_durability: bool = True
73
+ parameters: List[TaskParameter] = field(default_factory=list)
74
+
75
+ def to_dict(self) -> Dict[str, Any]:
76
+ return {
77
+ "name": self.name,
78
+ "description": self.description,
79
+ "output_format": self.output_format.value,
80
+ "output_schema": self.output_schema,
81
+ "retry_count": self.retry_count,
82
+ "timeout": self.timeout,
83
+ "enable_durability": self.enable_durability,
84
+ "parameters": [param.to_dict() for param in self.parameters]
85
+ }
86
+
87
+
88
+ @dataclass
89
+ class TaskResult:
90
+ """Result of task execution."""
91
+ task_name: str
92
+ status: TaskStatus
93
+ output: Any = None
94
+ error: Optional[str] = None
95
+ metadata: Dict[str, Any] = field(default_factory=dict)
96
+ started_at: Optional[datetime] = None
97
+ completed_at: Optional[datetime] = None
98
+ execution_time: Optional[float] = None
99
+
100
+ def to_dict(self) -> Dict[str, Any]:
101
+ return {
102
+ "task_name": self.task_name,
103
+ "status": self.status.value,
104
+ "output": self.output,
105
+ "error": self.error,
106
+ "metadata": self.metadata,
107
+ "started_at": self.started_at.isoformat() if self.started_at else None,
108
+ "completed_at": self.completed_at.isoformat() if self.completed_at else None,
109
+ "execution_time": self.execution_time
110
+ }
111
+
112
+
113
+ class TaskRegistry:
114
+ """Global registry for tasks."""
115
+ _tasks: Dict[str, 'Task'] = {}
116
+
117
+ @classmethod
118
+ def register(cls, task: 'Task') -> None:
119
+ """Register a task."""
120
+ cls._tasks[task.config.name] = task
121
+
122
+ @classmethod
123
+ def get(cls, name: str) -> Optional['Task']:
124
+ """Get a task by name."""
125
+ return cls._tasks.get(name)
126
+
127
+ @classmethod
128
+ def list_tasks(cls) -> List[str]:
129
+ """Get list of registered task names."""
130
+ return list(cls._tasks.keys())
131
+
132
+ @classmethod
133
+ def get_all(cls) -> Dict[str, 'Task']:
134
+ """Get all registered tasks."""
135
+ return cls._tasks.copy()
136
+
137
+
138
+ class OutputValidator(ABC):
139
+ """Base class for output validators."""
140
+
141
+ @abstractmethod
142
+ def validate(self, output: Any) -> Any:
143
+ """Validate and potentially transform output."""
144
+ pass
145
+
146
+
147
+ class JSONValidator(OutputValidator):
148
+ """Validates and parses JSON output."""
149
+
150
+ def __init__(self, schema: Optional[Dict[str, Any]] = None):
151
+ self.schema = schema
152
+
153
+ def validate(self, output: Any) -> Any:
154
+ """Validate JSON output."""
155
+ if isinstance(output, str):
156
+ try:
157
+ parsed = json.loads(output)
158
+ return self._validate_schema(parsed) if self.schema else parsed
159
+ except json.JSONDecodeError as e:
160
+ raise ValueError(f"Invalid JSON output: {e}")
161
+ elif isinstance(output, (dict, list)):
162
+ return self._validate_schema(output) if self.schema else output
163
+ else:
164
+ raise ValueError(f"Expected JSON-serializable output, got {type(output)}")
165
+
166
+ def _validate_schema(self, data: Any) -> Any:
167
+ """Basic schema validation (can be enhanced with jsonschema library)."""
168
+ if not self.schema:
169
+ return data
170
+
171
+ # Basic type checking
172
+ if "type" in self.schema:
173
+ expected_type = self.schema["type"]
174
+ if expected_type == "object" and not isinstance(data, dict):
175
+ raise ValueError(f"Expected object, got {type(data)}")
176
+ elif expected_type == "array" and not isinstance(data, list):
177
+ raise ValueError(f"Expected array, got {type(data)}")
178
+
179
+ # Check required properties for objects
180
+ if isinstance(data, dict) and "properties" in self.schema:
181
+ required = self.schema.get("required", [])
182
+ for prop in required:
183
+ if prop not in data:
184
+ raise ValueError(f"Missing required property: {prop}")
185
+
186
+ return data
187
+
188
+
189
+ class PydanticValidator(OutputValidator):
190
+ """Validates output using Pydantic models."""
191
+
192
+ def __init__(self, model_class: Type):
193
+ self.model_class = model_class
194
+
195
+ def validate(self, output: Any) -> Any:
196
+ """Validate using Pydantic model."""
197
+ try:
198
+ if isinstance(output, str):
199
+ # Try to parse as JSON first
200
+ try:
201
+ output = json.loads(output)
202
+ except json.JSONDecodeError:
203
+ pass
204
+
205
+ if isinstance(output, dict):
206
+ return self.model_class(**output)
207
+ else:
208
+ return self.model_class(output)
209
+ except Exception as e:
210
+ raise ValueError(f"Failed to validate with {self.model_class.__name__}: {e}")
211
+
212
+
213
+ class Task:
214
+ """
215
+ A task represents a structured, reusable operation with validation and error handling.
216
+
217
+ Tasks can be used for data extraction, processing, validation, and other operations
218
+ that benefit from structured input/output and automatic retry mechanisms.
219
+ """
220
+
221
+ def __init__(self, config: TaskConfig, func: Callable):
222
+ self.config = config
223
+ self.func = func
224
+ self._validator = self._create_validator()
225
+
226
+ # Extract parameter information from function signature
227
+ self._extract_parameters()
228
+
229
+ def _create_validator(self) -> Optional[OutputValidator]:
230
+ """Create appropriate validator based on configuration."""
231
+ if self.config.output_format == OutputFormat.JSON:
232
+ return JSONValidator(self.config.output_schema)
233
+ elif self.config.output_format == OutputFormat.PYDANTIC and self.config.output_schema:
234
+ # Output schema should contain the Pydantic model class
235
+ model_class = self.config.output_schema.get("model_class")
236
+ if model_class:
237
+ return PydanticValidator(model_class)
238
+ return None
239
+
240
+ def _extract_parameters(self) -> None:
241
+ """Extract parameter information from function signature."""
242
+ try:
243
+ sig = inspect.signature(self.func)
244
+
245
+ # Try to get type hints, but handle forward references gracefully
246
+ try:
247
+ type_hints = get_type_hints(self.func)
248
+ except (NameError, AttributeError):
249
+ # Fall back to raw annotations if type hints can't be resolved
250
+ type_hints = getattr(self.func, '__annotations__', {})
251
+
252
+ parameters = []
253
+ for param_name, param in sig.parameters.items():
254
+ if param_name in ['self', 'cls']:
255
+ continue
256
+
257
+ param_type = type_hints.get(param_name, str)
258
+ required = param.default == inspect.Parameter.empty
259
+ default = param.default if not required else None
260
+
261
+ parameters.append(TaskParameter(
262
+ name=param_name,
263
+ type=param_type,
264
+ required=required,
265
+ default=default
266
+ ))
267
+
268
+ self.config.parameters = parameters
269
+ except Exception as e:
270
+ logger.warning(f"Failed to extract parameters for task {self.config.name}: {e}")
271
+
272
+ async def run(self, *args, **kwargs) -> TaskResult:
273
+ """Execute the task with validation and error handling."""
274
+ task_result = TaskResult(
275
+ task_name=self.config.name,
276
+ status=TaskStatus.RUNNING,
277
+ started_at=datetime.utcnow()
278
+ )
279
+
280
+ with traced(f"task.{self.config.name}"):
281
+ try:
282
+ with span("task.execution") as task_span:
283
+ task_span.set_attribute("task.name", self.config.name)
284
+ task_span.set_attribute("task.durability", self.config.enable_durability)
285
+ task_span.set_attribute("task.output_format", self.config.output_format.value)
286
+
287
+ # Execute with or without durability
288
+ if self.config.enable_durability:
289
+ output = await self._run_durable(*args, **kwargs)
290
+ else:
291
+ output = await self._run_direct(*args, **kwargs)
292
+
293
+ # Validate output
294
+ if self._validator:
295
+ output = self._validator.validate(output)
296
+
297
+ # Complete task
298
+ task_result.status = TaskStatus.COMPLETED
299
+ task_result.output = output
300
+ task_result.completed_at = datetime.utcnow()
301
+ task_result.execution_time = (
302
+ task_result.completed_at - task_result.started_at
303
+ ).total_seconds()
304
+
305
+ task_span.set_attribute("task.status", "completed")
306
+ task_span.set_attribute("task.execution_time", task_result.execution_time)
307
+
308
+ log(TraceLevel.INFO, f"Task {self.config.name} completed successfully",
309
+ task_name=self.config.name, execution_time=task_result.execution_time)
310
+
311
+ return task_result
312
+
313
+ except Exception as e:
314
+ task_result.status = TaskStatus.FAILED
315
+ task_result.error = str(e)
316
+ task_result.completed_at = datetime.utcnow()
317
+ task_result.execution_time = (
318
+ task_result.completed_at - task_result.started_at
319
+ ).total_seconds()
320
+
321
+ logger.error(f"Task {self.config.name} failed: {e}")
322
+ log(TraceLevel.ERROR, f"Task {self.config.name} failed: {e}",
323
+ task_name=self.config.name, error_type=type(e).__name__)
324
+
325
+ return task_result
326
+
327
+ @durable.function
328
+ async def _run_durable(self, *args, **kwargs) -> Any:
329
+ """Run task with durability guarantees."""
330
+ return await self._run_direct(*args, **kwargs)
331
+
332
+ async def _run_direct(self, *args, **kwargs) -> Any:
333
+ """Run task directly without durability."""
334
+ if inspect.iscoroutinefunction(self.func):
335
+ return await self.func(*args, **kwargs)
336
+ else:
337
+ # Run sync function in thread pool
338
+ import asyncio
339
+ loop = asyncio.get_event_loop()
340
+ return await loop.run_in_executor(None, self.func, *args, **kwargs)
341
+
342
+
343
+ def task(
344
+ name: Optional[str] = None,
345
+ description: Optional[str] = None,
346
+ output_format: OutputFormat = OutputFormat.RAW,
347
+ output_schema: Optional[Dict[str, Any]] = None,
348
+ retry_count: int = 3,
349
+ timeout: Optional[float] = None,
350
+ enable_durability: bool = True,
351
+ ) -> Callable:
352
+ """
353
+ Decorator to create a task from a function.
354
+
355
+ Args:
356
+ name: Task name (defaults to function name)
357
+ description: Task description
358
+ output_format: Expected output format (json, pydantic, string, raw)
359
+ output_schema: Schema for output validation
360
+ retry_count: Number of retry attempts on failure
361
+ timeout: Execution timeout in seconds
362
+ enable_durability: Whether to enable durable execution
363
+
364
+ Example:
365
+ ```python
366
+ @task(name="extract_data", output_format=OutputFormat.JSON)
367
+ def extract_json_data(text: str) -> dict:
368
+ '''Extract structured data from text.'''
369
+ # Processing logic
370
+ return {"extracted": "data"}
371
+
372
+ # Use the task
373
+ result = await extract_json_data.run("input text")
374
+ print(result.output) # {"extracted": "data"}
375
+ ```
376
+ """
377
+ def decorator(func: Callable) -> 'TaskFunction':
378
+ task_name = name or func.__name__
379
+ task_description = description or inspect.getdoc(func) or f"Task: {task_name}"
380
+
381
+ config = TaskConfig(
382
+ name=task_name,
383
+ description=task_description,
384
+ output_format=output_format,
385
+ output_schema=output_schema,
386
+ retry_count=retry_count,
387
+ timeout=timeout,
388
+ enable_durability=enable_durability
389
+ )
390
+
391
+ task_instance = Task(config, func)
392
+ TaskRegistry.register(task_instance)
393
+
394
+ # Create a wrapper that preserves the original function interface
395
+ # but adds task capabilities
396
+ return TaskFunction(task_instance, func)
397
+
398
+ return decorator
399
+
400
+
401
+ class TaskFunction:
402
+ """Wrapper that makes a task behave like a function while adding task capabilities."""
403
+
404
+ def __init__(self, task: Task, original_func: Callable):
405
+ self.task = task
406
+ self.original_func = original_func
407
+ self.__name__ = original_func.__name__
408
+ self.__doc__ = original_func.__doc__
409
+
410
+ async def __call__(self, *args, **kwargs) -> Any:
411
+ """Direct function call returns just the output."""
412
+ result = await self.task.run(*args, **kwargs)
413
+ if result.status == TaskStatus.COMPLETED:
414
+ return result.output
415
+ else:
416
+ raise RuntimeError(f"Task failed: {result.error}")
417
+
418
+ async def run(self, *args, **kwargs) -> TaskResult:
419
+ """Run as task and return full TaskResult."""
420
+ return await self.task.run(*args, **kwargs)
421
+
422
+ @property
423
+ def config(self) -> TaskConfig:
424
+ """Get task configuration."""
425
+ return self.task.config
426
+
427
+ def to_dict(self) -> Dict[str, Any]:
428
+ """Export task configuration."""
429
+ return self.task.config.to_dict()
430
+
431
+
432
+ # Convenience functions for common task types
433
+ def json_extraction_task(
434
+ name: Optional[str] = None,
435
+ description: Optional[str] = None,
436
+ schema: Optional[Dict[str, Any]] = None,
437
+ **kwargs
438
+ ) -> Callable:
439
+ """Create a task for JSON data extraction."""
440
+ return task(
441
+ name=name,
442
+ description=description,
443
+ output_format=OutputFormat.JSON,
444
+ output_schema=schema,
445
+ **kwargs
446
+ )
447
+
448
+
449
+ def pydantic_task(
450
+ model_class: Type,
451
+ name: Optional[str] = None,
452
+ description: Optional[str] = None,
453
+ **kwargs
454
+ ) -> Callable:
455
+ """Create a task with Pydantic output validation."""
456
+ return task(
457
+ name=name,
458
+ description=description,
459
+ output_format=OutputFormat.PYDANTIC,
460
+ output_schema={"model_class": model_class},
461
+ **kwargs
462
+ )
463
+
464
+
465
+ def string_task(
466
+ name: Optional[str] = None,
467
+ description: Optional[str] = None,
468
+ **kwargs
469
+ ) -> Callable:
470
+ """Create a task that returns string output."""
471
+ return task(
472
+ name=name,
473
+ description=description,
474
+ output_format=OutputFormat.STRING,
475
+ **kwargs
476
+ )