agnt5 0.2.8a10__cp310-abi3-manylinux_2_34_x86_64.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/context.py ADDED
@@ -0,0 +1,178 @@
1
+ """Context implementation for AGNT5 SDK."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import contextvars
6
+ import logging
7
+ from typing import Any, Awaitable, Callable, Dict, List, Optional, TypeVar, Union
8
+
9
+ T = TypeVar("T")
10
+
11
+ # Task-local context variable for automatic context propagation
12
+ # This is NOT a global variable - contextvars provide task-isolated storage
13
+ # Each asyncio task gets its own independent copy, preventing cross-contamination
14
+ _current_context: contextvars.ContextVar[Optional["Context"]] = contextvars.ContextVar(
15
+ "_current_context", default=None
16
+ )
17
+
18
+
19
+ class _CorrelationFilter(logging.Filter):
20
+ """Inject correlation IDs (run_id, trace_id, span_id) into every log record."""
21
+
22
+ def __init__(self, runtime_context: Any) -> None:
23
+ super().__init__()
24
+ self.runtime_context = runtime_context
25
+
26
+ def filter(self, record: logging.LogRecord) -> bool:
27
+ """Add correlation IDs as extra fields to the log record."""
28
+ record.run_id = self.runtime_context.run_id
29
+ if self.runtime_context.trace_id:
30
+ record.trace_id = self.runtime_context.trace_id
31
+ if self.runtime_context.span_id:
32
+ record.span_id = self.runtime_context.span_id
33
+ return True
34
+
35
+
36
+ class Context:
37
+ """
38
+ Base context providing common functionality.
39
+
40
+ Provides:
41
+ - Logging with correlation IDs
42
+ - Execution metadata (run_id, attempt)
43
+ - Runtime context for tracing
44
+
45
+ Extended by:
46
+ - FunctionContext: Minimal context for stateless functions
47
+ - WorkflowContext: Context for durable workflows
48
+ """
49
+
50
+ def __init__(
51
+ self,
52
+ run_id: str,
53
+ attempt: int = 0,
54
+ runtime_context: Optional[Any] = None,
55
+ ) -> None:
56
+ """
57
+ Initialize base context.
58
+
59
+ Args:
60
+ run_id: Unique execution identifier
61
+ attempt: Retry attempt number (0-indexed)
62
+ runtime_context: RuntimeContext for trace correlation
63
+ """
64
+ self._run_id = run_id
65
+ self._attempt = attempt
66
+ self._runtime_context = runtime_context
67
+
68
+ # Create logger with correlation
69
+ self._logger = logging.getLogger(f"agnt5.{run_id}")
70
+ from ._telemetry import setup_context_logger
71
+ setup_context_logger(self._logger)
72
+
73
+ if runtime_context:
74
+ self._logger.addFilter(_CorrelationFilter(runtime_context))
75
+
76
+ @property
77
+ def run_id(self) -> str:
78
+ """Unique execution identifier."""
79
+ return self._run_id
80
+
81
+ @property
82
+ def attempt(self) -> int:
83
+ """Current retry attempt (0-indexed)."""
84
+ return self._attempt
85
+
86
+ @property
87
+ def logger(self) -> logging.Logger:
88
+ """Full logger for .debug(), .warning(), .error(), etc."""
89
+ return self._logger
90
+
91
+
92
+ def get_current_context() -> Optional[Context]:
93
+ """
94
+ Get the current execution context from task-local storage.
95
+
96
+ This function retrieves the context that was set by the nearest enclosing
97
+ decorator (@function, @workflow) or Agent.run() call in the current asyncio task.
98
+
99
+ Returns:
100
+ Current Context if available (WorkflowContext, FunctionContext, AgentContext),
101
+ None if no context is set (e.g., running outside AGNT5 execution)
102
+
103
+ Example:
104
+ >>> ctx = get_current_context()
105
+ >>> if ctx:
106
+ ... ctx.logger.info("Logging from anywhere in the call stack!")
107
+ ... runtime = ctx._runtime_context # Access tracing context
108
+
109
+ Note:
110
+ This uses Python's contextvars which provide task-local (NOT global) storage.
111
+ Each asyncio task has its own isolated context, preventing cross-contamination
112
+ between concurrent executions.
113
+ """
114
+ return _current_context.get()
115
+
116
+
117
+ def set_current_context(ctx: Context) -> contextvars.Token:
118
+ """
119
+ Set the current execution context in task-local storage.
120
+
121
+ This is typically called by decorators and framework code, not by user code.
122
+ Returns a token that can be used to reset the context to its previous value.
123
+
124
+ Args:
125
+ ctx: Context to set as current
126
+
127
+ Returns:
128
+ Token for resetting the context later (use with contextvars.Token.reset())
129
+
130
+ Example:
131
+ >>> token = set_current_context(my_context)
132
+ >>> try:
133
+ ... # Context is available via get_current_context()
134
+ ... do_work()
135
+ >>> finally:
136
+ ... _current_context.reset(token) # Restore previous context
137
+
138
+ Note:
139
+ Always use try/finally to ensure context is properly reset, even if
140
+ an exception occurs. This prevents context leakage between executions.
141
+ """
142
+ return _current_context.set(ctx)
143
+
144
+
145
+ def get_workflow_context() -> Optional["WorkflowContext"]:
146
+ """
147
+ Get the WorkflowContext from the current context or its parent chain.
148
+
149
+ This function traverses the context hierarchy to find a WorkflowContext,
150
+ which is needed for emitting workflow checkpoints from nested contexts
151
+ like AgentContext or FunctionContext.
152
+
153
+ Returns:
154
+ WorkflowContext if found in the context chain, None otherwise
155
+
156
+ Example:
157
+ >>> # Inside an agent called from a workflow
158
+ >>> workflow_ctx = get_workflow_context()
159
+ >>> if workflow_ctx:
160
+ ... workflow_ctx._send_checkpoint("workflow.lm.started", {...})
161
+ """
162
+ from .workflow import WorkflowContext
163
+
164
+ ctx = get_current_context()
165
+
166
+ # Traverse up the context chain looking for WorkflowContext
167
+ while ctx is not None:
168
+ if isinstance(ctx, WorkflowContext):
169
+ return ctx
170
+ # Check if this context has a parent_context attribute
171
+ if hasattr(ctx, 'parent_context'):
172
+ ctx = ctx.parent_context
173
+ else:
174
+ break
175
+
176
+ return None
177
+
178
+