agenta 0.68.0__py3-none-any.whl → 0.72.4__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.
- agenta/__init__.py +66 -36
- agenta/sdk/agenta_init.py +88 -10
- agenta/sdk/assets.py +73 -10
- agenta/sdk/decorators/serving.py +29 -31
- agenta/sdk/decorators/tracing.py +51 -30
- agenta/sdk/engines/tracing/processors.py +23 -12
- agenta/sdk/litellm/litellm.py +38 -30
- agenta/sdk/middleware/auth.py +19 -4
- agenta/sdk/middleware/config.py +3 -1
- agenta/sdk/middleware/otel.py +3 -4
- agenta/sdk/middleware/vault.py +20 -5
- agenta/sdk/middlewares/routing/otel.py +1 -1
- agenta/sdk/middlewares/running/vault.py +1 -1
- agenta/sdk/models/shared.py +1 -1
- agenta/sdk/tracing/exporters.py +1 -0
- agenta/sdk/tracing/processors.py +48 -40
- agenta/sdk/tracing/propagation.py +9 -12
- agenta/sdk/tracing/tracing.py +89 -0
- agenta/sdk/types.py +6 -2
- agenta/sdk/workflows/runners/daytona.py +0 -6
- agenta/sdk/workflows/runners/registry.py +19 -2
- {agenta-0.68.0.dist-info → agenta-0.72.4.dist-info}/METADATA +3 -2
- {agenta-0.68.0.dist-info → agenta-0.72.4.dist-info}/RECORD +24 -24
- {agenta-0.68.0.dist-info → agenta-0.72.4.dist-info}/WHEEL +0 -0
agenta/__init__.py
CHANGED
|
@@ -1,52 +1,50 @@
|
|
|
1
1
|
from typing import Any, Callable, Optional
|
|
2
2
|
|
|
3
|
-
from .sdk.utils.preinit import PreInitObject
|
|
4
|
-
|
|
5
|
-
from agenta.client import AgentaApi, AsyncAgentaApi
|
|
6
3
|
import agenta.client.backend.types as client_types # pylint: disable=wrong-import-order
|
|
4
|
+
from agenta.client import AgentaApi, AsyncAgentaApi
|
|
7
5
|
|
|
8
|
-
from .sdk
|
|
9
|
-
MCField,
|
|
10
|
-
DictInput,
|
|
11
|
-
MultipleChoice,
|
|
12
|
-
FloatParam,
|
|
13
|
-
IntParam,
|
|
14
|
-
MultipleChoiceParam,
|
|
15
|
-
GroupedMultipleChoiceParam,
|
|
16
|
-
MessagesInput,
|
|
17
|
-
TextParam,
|
|
18
|
-
FileInputURL,
|
|
19
|
-
BinaryParam,
|
|
20
|
-
Prompt,
|
|
21
|
-
PromptTemplate,
|
|
22
|
-
)
|
|
6
|
+
from .sdk import assets as assets
|
|
23
7
|
|
|
24
|
-
|
|
25
|
-
from .sdk
|
|
26
|
-
from .sdk
|
|
27
|
-
from .sdk.
|
|
28
|
-
from .sdk.
|
|
29
|
-
from .sdk.
|
|
8
|
+
# evaluations
|
|
9
|
+
from .sdk import testsets as testsets
|
|
10
|
+
from .sdk import tracer
|
|
11
|
+
from .sdk.agenta_init import AgentaSingleton, Config
|
|
12
|
+
from .sdk.agenta_init import init as _init
|
|
13
|
+
from .sdk.context.running import workflow_mode_enabled
|
|
30
14
|
from .sdk.decorators.running import (
|
|
31
|
-
workflow,
|
|
32
15
|
application,
|
|
33
16
|
evaluator,
|
|
17
|
+
workflow,
|
|
34
18
|
)
|
|
35
|
-
from .sdk.decorators.serving import
|
|
36
|
-
from .sdk.
|
|
19
|
+
from .sdk.decorators.serving import app, route
|
|
20
|
+
from .sdk.decorators.tracing import instrument
|
|
37
21
|
from .sdk.litellm import litellm as callbacks
|
|
38
22
|
from .sdk.managers.apps import AppManager
|
|
39
|
-
from .sdk.managers.vault import VaultManager
|
|
40
|
-
from .sdk.managers.secrets import SecretsManager
|
|
41
23
|
from .sdk.managers.config import ConfigManager
|
|
42
|
-
from .sdk.managers.variant import VariantManager
|
|
43
24
|
from .sdk.managers.deployment import DeploymentManager
|
|
44
|
-
from .sdk import
|
|
45
|
-
from .sdk import
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
from .sdk import
|
|
49
|
-
|
|
25
|
+
from .sdk.managers.secrets import SecretsManager
|
|
26
|
+
from .sdk.managers.variant import VariantManager
|
|
27
|
+
from .sdk.managers.vault import VaultManager
|
|
28
|
+
from .sdk.tracing import Tracing, get_tracer
|
|
29
|
+
from .sdk.tracing.conventions import Reference
|
|
30
|
+
from .sdk.types import (
|
|
31
|
+
BinaryParam,
|
|
32
|
+
DictInput,
|
|
33
|
+
FileInputURL,
|
|
34
|
+
FloatParam,
|
|
35
|
+
GroupedMultipleChoiceParam,
|
|
36
|
+
IntParam,
|
|
37
|
+
MCField,
|
|
38
|
+
MessagesInput,
|
|
39
|
+
MultipleChoice,
|
|
40
|
+
MultipleChoiceParam,
|
|
41
|
+
Prompt,
|
|
42
|
+
PromptTemplate,
|
|
43
|
+
TextParam,
|
|
44
|
+
)
|
|
45
|
+
from .sdk.utils.costs import calculate_token_usage
|
|
46
|
+
from .sdk.utils.logging import get_module_logger
|
|
47
|
+
from .sdk.utils.preinit import PreInitObject
|
|
50
48
|
|
|
51
49
|
config = PreInitObject("agenta.config", Config)
|
|
52
50
|
DEFAULT_AGENTA_SINGLETON_INSTANCE = AgentaSingleton()
|
|
@@ -88,3 +86,35 @@ def init(
|
|
|
88
86
|
|
|
89
87
|
tracing = DEFAULT_AGENTA_SINGLETON_INSTANCE.tracing # type: ignore
|
|
90
88
|
tracer = get_tracer(tracing)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def get_trace_url(trace_id: Optional[str] = None) -> str:
|
|
92
|
+
"""
|
|
93
|
+
Build a URL to view the current trace in the Agenta UI.
|
|
94
|
+
|
|
95
|
+
Automatically extracts the trace ID from the current tracing context.
|
|
96
|
+
Can also accept an explicit trace_id if needed.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
trace_id: Optional trace ID (hex string format). If not provided,
|
|
100
|
+
it will be automatically extracted from the current trace context.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
The full URL to view the trace in the observability dashboard
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
RuntimeError: If the SDK is not initialized, no active trace context exists,
|
|
107
|
+
or scope info cannot be fetched
|
|
108
|
+
|
|
109
|
+
Example:
|
|
110
|
+
>>> import agenta as ag
|
|
111
|
+
>>> ag.init(api_key="xxx")
|
|
112
|
+
>>>
|
|
113
|
+
>>> @ag.instrument()
|
|
114
|
+
>>> def my_function():
|
|
115
|
+
>>> # Get URL for the current trace
|
|
116
|
+
>>> url = ag.tracing.get_trace_url()
|
|
117
|
+
>>> print(url)
|
|
118
|
+
>>> return "result"
|
|
119
|
+
"""
|
|
120
|
+
return DEFAULT_AGENTA_SINGLETON_INSTANCE.tracing.get_trace_url(trace_id)
|
agenta/sdk/agenta_init.py
CHANGED
|
@@ -1,16 +1,15 @@
|
|
|
1
|
-
import toml
|
|
2
|
-
from os import getenv
|
|
3
|
-
from typing import Optional, Callable, Any
|
|
4
1
|
from importlib.metadata import version
|
|
2
|
+
from os import getenv
|
|
3
|
+
from typing import Any, Callable, Optional
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
from agenta.sdk.utils.logging import get_module_logger
|
|
5
|
+
import requests
|
|
6
|
+
import toml
|
|
9
7
|
from agenta.client.client import AgentaApi, AsyncAgentaApi
|
|
10
|
-
|
|
11
|
-
from agenta.sdk.tracing import Tracing
|
|
12
8
|
from agenta.sdk.contexts.routing import RoutingContext
|
|
13
|
-
|
|
9
|
+
from agenta.sdk.tracing import Tracing
|
|
10
|
+
from agenta.sdk.utils.globals import set_global
|
|
11
|
+
from agenta.sdk.utils.helpers import parse_url
|
|
12
|
+
from agenta.sdk.utils.logging import get_module_logger
|
|
14
13
|
|
|
15
14
|
log = get_module_logger(__name__)
|
|
16
15
|
|
|
@@ -19,6 +18,7 @@ class AgentaSingleton:
|
|
|
19
18
|
"""Singleton class to save all the "global variables" for the sdk."""
|
|
20
19
|
|
|
21
20
|
_instance = None
|
|
21
|
+
_initialized = False
|
|
22
22
|
config = None
|
|
23
23
|
tracing = None
|
|
24
24
|
|
|
@@ -26,6 +26,11 @@ class AgentaSingleton:
|
|
|
26
26
|
async_api = None
|
|
27
27
|
|
|
28
28
|
def __init__(self):
|
|
29
|
+
# Only initialize once
|
|
30
|
+
if AgentaSingleton._initialized:
|
|
31
|
+
return
|
|
32
|
+
|
|
33
|
+
AgentaSingleton._initialized = True
|
|
29
34
|
self.host = None
|
|
30
35
|
self.api_url = None
|
|
31
36
|
self.api_key = None
|
|
@@ -33,6 +38,11 @@ class AgentaSingleton:
|
|
|
33
38
|
self.scope_type = None
|
|
34
39
|
self.scope_id = None
|
|
35
40
|
|
|
41
|
+
# Cached scope information for URL building
|
|
42
|
+
self.organization_id: Optional[str] = None
|
|
43
|
+
self.workspace_id: Optional[str] = None
|
|
44
|
+
self.project_id: Optional[str] = None
|
|
45
|
+
|
|
36
46
|
def __new__(cls):
|
|
37
47
|
if not cls._instance:
|
|
38
48
|
cls._instance = super(AgentaSingleton, cls).__new__(cls)
|
|
@@ -70,6 +80,10 @@ class AgentaSingleton:
|
|
|
70
80
|
|
|
71
81
|
"""
|
|
72
82
|
|
|
83
|
+
# Idempotency check: if already initialized, skip re-initialization
|
|
84
|
+
if self.tracing and self.api and self.async_api:
|
|
85
|
+
return
|
|
86
|
+
|
|
73
87
|
log.info("Agenta - SDK ver: %s", version("agenta"))
|
|
74
88
|
|
|
75
89
|
config = {}
|
|
@@ -100,7 +114,7 @@ class AgentaSingleton:
|
|
|
100
114
|
|
|
101
115
|
try:
|
|
102
116
|
assert _api_url and isinstance(_api_url, str), (
|
|
103
|
-
"API URL is required. Please
|
|
117
|
+
"API URL is required. Please set AGENTA_API_URL environment variable or pass api_url parameter in ag.init()."
|
|
104
118
|
)
|
|
105
119
|
self.host = _host
|
|
106
120
|
self.api_url = _api_url
|
|
@@ -118,6 +132,11 @@ class AgentaSingleton:
|
|
|
118
132
|
or None # NO FALLBACK
|
|
119
133
|
)
|
|
120
134
|
|
|
135
|
+
if self.api_key is None:
|
|
136
|
+
log.error(
|
|
137
|
+
"API key is required. Please set AGENTA_API_KEY environment variable or pass api_key parameter in ag.init()."
|
|
138
|
+
)
|
|
139
|
+
|
|
121
140
|
log.info("Agenta - API URL: %s", self.api_url)
|
|
122
141
|
|
|
123
142
|
self.scope_type = (
|
|
@@ -159,6 +178,65 @@ class AgentaSingleton:
|
|
|
159
178
|
api_key=self.api_key,
|
|
160
179
|
)
|
|
161
180
|
|
|
181
|
+
# Reset cached scope info on re-init
|
|
182
|
+
self.organization_id = None
|
|
183
|
+
self.workspace_id = None
|
|
184
|
+
self.project_id = None
|
|
185
|
+
|
|
186
|
+
def resolve_scopes(self) -> Optional[tuple[str, str, str]]:
|
|
187
|
+
"""Fetch and cache workspace_id and project_id from the API."""
|
|
188
|
+
if (
|
|
189
|
+
self.organization_id is not None
|
|
190
|
+
and self.workspace_id is not None
|
|
191
|
+
and self.project_id is not None
|
|
192
|
+
):
|
|
193
|
+
return
|
|
194
|
+
|
|
195
|
+
if self.api_url is None or self.api_key is None:
|
|
196
|
+
log.error("API URL or API key is not set. Please call ag.init() first.")
|
|
197
|
+
return
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
response = requests.get(
|
|
201
|
+
f"{self.api_url}/projects/current",
|
|
202
|
+
headers={"Authorization": f"ApiKey {self.api_key}"},
|
|
203
|
+
timeout=10,
|
|
204
|
+
)
|
|
205
|
+
response.raise_for_status()
|
|
206
|
+
|
|
207
|
+
project_info = response.json()
|
|
208
|
+
|
|
209
|
+
if not project_info:
|
|
210
|
+
log.error(
|
|
211
|
+
"No project context found. Please ensure your API key is valid."
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
self.organization_id = project_info.get("organization_id")
|
|
215
|
+
self.workspace_id = project_info.get("workspace_id")
|
|
216
|
+
self.project_id = project_info.get("project_id")
|
|
217
|
+
|
|
218
|
+
if (
|
|
219
|
+
not self.organization_id
|
|
220
|
+
and not self.workspace_id
|
|
221
|
+
or not self.project_id
|
|
222
|
+
):
|
|
223
|
+
log.error(
|
|
224
|
+
"Could not determine organization/workspace/project from API response."
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
except Exception as e:
|
|
228
|
+
log.error(f"Failed to fetch scope information: {e}")
|
|
229
|
+
return
|
|
230
|
+
|
|
231
|
+
if self.organization_id and self.workspace_id and self.project_id:
|
|
232
|
+
return (
|
|
233
|
+
self.organization_id,
|
|
234
|
+
self.workspace_id,
|
|
235
|
+
self.project_id,
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
return None
|
|
239
|
+
|
|
162
240
|
|
|
163
241
|
class Config:
|
|
164
242
|
def __init__(
|
agenta/sdk/assets.py
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
from typing import Dict, Optional, Tuple
|
|
2
|
+
|
|
3
|
+
from litellm import cost_calculator
|
|
4
|
+
|
|
5
|
+
|
|
1
6
|
supported_llm_models = {
|
|
2
7
|
"anthropic": [
|
|
3
8
|
"anthropic/claude-sonnet-4-5",
|
|
@@ -28,6 +33,7 @@ supported_llm_models = {
|
|
|
28
33
|
],
|
|
29
34
|
"gemini": [
|
|
30
35
|
"gemini/gemini-3-pro-preview",
|
|
36
|
+
"gemini/gemini-3-flash-preview",
|
|
31
37
|
"gemini/gemini-2.5-pro",
|
|
32
38
|
"gemini/gemini-2.5-pro-preview-05-06",
|
|
33
39
|
"gemini/gemini-2.5-flash",
|
|
@@ -64,21 +70,26 @@ supported_llm_models = {
|
|
|
64
70
|
"mistral/mistral-large-latest",
|
|
65
71
|
],
|
|
66
72
|
"openai": [
|
|
67
|
-
"gpt-5",
|
|
73
|
+
"gpt-5.2-pro",
|
|
74
|
+
"gpt-5.2-chat-latest",
|
|
75
|
+
"gpt-5.2",
|
|
76
|
+
"gpt-5.1-chat-latest",
|
|
68
77
|
"gpt-5.1",
|
|
69
|
-
"gpt-5-
|
|
78
|
+
"gpt-5-pro",
|
|
70
79
|
"gpt-5-nano",
|
|
80
|
+
"gpt-5-mini",
|
|
81
|
+
"gpt-5",
|
|
82
|
+
"o4-mini",
|
|
71
83
|
"gpt-4.5-preview",
|
|
72
|
-
"gpt-
|
|
73
|
-
"gpt-
|
|
74
|
-
"gpt-4",
|
|
75
|
-
"gpt-4o",
|
|
84
|
+
"gpt-4.1-nano",
|
|
85
|
+
"gpt-4.1-mini",
|
|
86
|
+
"gpt-4.1",
|
|
76
87
|
"gpt-4o-mini",
|
|
88
|
+
"gpt-4o",
|
|
77
89
|
"gpt-4-1106-preview",
|
|
78
|
-
"gpt-4
|
|
79
|
-
"gpt-
|
|
80
|
-
"gpt-
|
|
81
|
-
"o4-mini",
|
|
90
|
+
"gpt-4",
|
|
91
|
+
"gpt-3.5-turbo-1106",
|
|
92
|
+
"gpt-3.5-turbo",
|
|
82
93
|
],
|
|
83
94
|
"openrouter": [
|
|
84
95
|
"openrouter/qwen/qwen3-235b-a22b",
|
|
@@ -200,6 +211,58 @@ supported_llm_models = {
|
|
|
200
211
|
|
|
201
212
|
providers_list = list(supported_llm_models.keys())
|
|
202
213
|
|
|
214
|
+
|
|
215
|
+
def _get_model_costs(model: str) -> Optional[Tuple[float, float]]:
|
|
216
|
+
"""
|
|
217
|
+
Get the input and output costs per 1M tokens for a model.
|
|
218
|
+
|
|
219
|
+
Uses litellm's cost_calculator (same as tracing/inline.py) for consistency.
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
model: The model name (e.g., "gpt-4o" or "anthropic/claude-3-opus-20240229")
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
Tuple of (input_cost, output_cost) per 1M tokens, or None if not found.
|
|
226
|
+
"""
|
|
227
|
+
try:
|
|
228
|
+
costs = cost_calculator.cost_per_token(
|
|
229
|
+
model=model,
|
|
230
|
+
prompt_tokens=1_000_000,
|
|
231
|
+
completion_tokens=1_000_000,
|
|
232
|
+
)
|
|
233
|
+
if costs:
|
|
234
|
+
input_cost, output_cost = costs
|
|
235
|
+
if input_cost > 0 or output_cost > 0:
|
|
236
|
+
return (input_cost, output_cost)
|
|
237
|
+
except Exception:
|
|
238
|
+
pass
|
|
239
|
+
return None
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _build_model_metadata() -> Dict[str, Dict[str, Dict[str, float]]]:
|
|
243
|
+
"""
|
|
244
|
+
Build metadata dictionary with costs for all supported models.
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
Nested dict: {provider: {model: {"input": cost, "output": cost}}}
|
|
248
|
+
"""
|
|
249
|
+
metadata: Dict[str, Dict[str, Dict[str, float]]] = {}
|
|
250
|
+
|
|
251
|
+
for provider, models in supported_llm_models.items():
|
|
252
|
+
metadata[provider] = {}
|
|
253
|
+
for model in models:
|
|
254
|
+
costs = _get_model_costs(model)
|
|
255
|
+
if costs:
|
|
256
|
+
metadata[provider][model] = {
|
|
257
|
+
"input": costs[0],
|
|
258
|
+
"output": costs[1],
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return metadata
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
model_metadata = _build_model_metadata()
|
|
265
|
+
|
|
203
266
|
model_to_provider_mapping = {
|
|
204
267
|
model: provider
|
|
205
268
|
for provider, models in supported_llm_models.items()
|
agenta/sdk/decorators/serving.py
CHANGED
|
@@ -1,53 +1,51 @@
|
|
|
1
|
-
from
|
|
1
|
+
from asyncio import sleep
|
|
2
|
+
from functools import wraps
|
|
2
3
|
from inspect import (
|
|
4
|
+
Parameter,
|
|
5
|
+
Signature,
|
|
6
|
+
isasyncgen,
|
|
3
7
|
iscoroutinefunction,
|
|
4
8
|
isgenerator,
|
|
5
|
-
isasyncgen,
|
|
6
9
|
signature,
|
|
7
|
-
Signature,
|
|
8
|
-
Parameter,
|
|
9
10
|
)
|
|
10
|
-
from
|
|
11
|
+
from os import environ
|
|
11
12
|
from traceback import format_exception
|
|
12
|
-
from
|
|
13
|
+
from typing import Any, Callable, Dict, List, Optional, Tuple, Type
|
|
13
14
|
from uuid import UUID
|
|
14
|
-
from pydantic import BaseModel, HttpUrl, ValidationError
|
|
15
|
-
from os import environ
|
|
16
|
-
|
|
17
|
-
from starlette.responses import (
|
|
18
|
-
Response as StarletteResponse,
|
|
19
|
-
StreamingResponse,
|
|
20
|
-
)
|
|
21
|
-
from fastapi import Body, FastAPI, HTTPException, Request
|
|
22
|
-
|
|
23
|
-
from agenta.sdk.middleware.mock import MockMiddleware
|
|
24
|
-
from agenta.sdk.middleware.inline import InlineMiddleware
|
|
25
|
-
from agenta.sdk.middleware.vault import VaultMiddleware
|
|
26
|
-
from agenta.sdk.middleware.config import ConfigMiddleware
|
|
27
|
-
from agenta.sdk.middleware.otel import OTelMiddleware
|
|
28
|
-
from agenta.sdk.middleware.auth import AuthHTTPMiddleware
|
|
29
|
-
from agenta.sdk.middleware.cors import CORSMiddleware
|
|
30
15
|
|
|
16
|
+
import agenta as ag
|
|
31
17
|
from agenta.sdk.contexts.routing import (
|
|
32
|
-
routing_context_manager,
|
|
33
18
|
RoutingContext,
|
|
19
|
+
routing_context_manager,
|
|
34
20
|
)
|
|
35
21
|
from agenta.sdk.contexts.tracing import (
|
|
36
|
-
tracing_context_manager,
|
|
37
22
|
TracingContext,
|
|
23
|
+
tracing_context_manager,
|
|
38
24
|
)
|
|
25
|
+
from agenta.sdk.middleware.auth import AuthHTTPMiddleware
|
|
26
|
+
from agenta.sdk.middleware.config import ConfigMiddleware
|
|
27
|
+
from agenta.sdk.middleware.cors import CORSMiddleware
|
|
28
|
+
from agenta.sdk.middleware.inline import InlineMiddleware
|
|
29
|
+
from agenta.sdk.middleware.mock import MockMiddleware
|
|
30
|
+
from agenta.sdk.middleware.otel import OTelMiddleware
|
|
31
|
+
from agenta.sdk.middleware.vault import VaultMiddleware
|
|
39
32
|
from agenta.sdk.router import router
|
|
40
|
-
from agenta.sdk.utils.exceptions import suppress, display_exception
|
|
41
|
-
from agenta.sdk.utils.logging import get_module_logger
|
|
42
|
-
from agenta.sdk.utils.helpers import get_current_version
|
|
43
33
|
from agenta.sdk.types import (
|
|
44
|
-
MultipleChoice,
|
|
45
34
|
BaseResponse,
|
|
35
|
+
MultipleChoice,
|
|
46
36
|
StreamResponse,
|
|
47
|
-
MCField,
|
|
48
37
|
)
|
|
49
|
-
|
|
50
|
-
|
|
38
|
+
from agenta.sdk.utils.exceptions import display_exception, suppress
|
|
39
|
+
from agenta.sdk.utils.helpers import get_current_version
|
|
40
|
+
from agenta.sdk.utils.logging import get_module_logger
|
|
41
|
+
from fastapi import Body, FastAPI, HTTPException, Request
|
|
42
|
+
from pydantic import BaseModel, HttpUrl, ValidationError
|
|
43
|
+
from starlette.responses import (
|
|
44
|
+
Response as StarletteResponse,
|
|
45
|
+
)
|
|
46
|
+
from starlette.responses import (
|
|
47
|
+
StreamingResponse,
|
|
48
|
+
)
|
|
51
49
|
|
|
52
50
|
log = get_module_logger(__name__)
|
|
53
51
|
|
agenta/sdk/decorators/tracing.py
CHANGED
|
@@ -1,36 +1,27 @@
|
|
|
1
1
|
# /agenta/sdk/decorators/tracing.py
|
|
2
2
|
|
|
3
|
-
from typing import Callable, Optional, Any, Dict, List, Union
|
|
4
|
-
|
|
5
|
-
from opentelemetry import context as otel_context
|
|
6
|
-
from opentelemetry.context import attach, detach
|
|
7
|
-
|
|
8
|
-
|
|
9
3
|
from functools import wraps
|
|
10
|
-
from itertools import chain
|
|
11
4
|
from inspect import (
|
|
12
5
|
getfullargspec,
|
|
6
|
+
isasyncgenfunction,
|
|
13
7
|
iscoroutinefunction,
|
|
14
8
|
isgeneratorfunction,
|
|
15
|
-
isasyncgenfunction,
|
|
16
9
|
)
|
|
10
|
+
from itertools import chain
|
|
11
|
+
from typing import Any, Callable, Dict, List, Optional, Union
|
|
17
12
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
from opentelemetry import baggage
|
|
21
|
-
from opentelemetry.context import attach, detach, get_current
|
|
22
|
-
from opentelemetry.baggage import set_baggage, get_all
|
|
23
|
-
|
|
24
|
-
from agenta.sdk.utils.logging import get_module_logger
|
|
25
|
-
from agenta.sdk.utils.exceptions import suppress
|
|
13
|
+
import agenta as ag
|
|
26
14
|
from agenta.sdk.contexts.tracing import (
|
|
27
15
|
TracingContext,
|
|
28
16
|
tracing_context_manager,
|
|
29
17
|
)
|
|
30
18
|
from agenta.sdk.tracing.conventions import parse_span_kind
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
19
|
+
from agenta.sdk.utils.exceptions import suppress
|
|
20
|
+
from agenta.sdk.utils.logging import get_module_logger
|
|
21
|
+
from opentelemetry import context as otel_context
|
|
22
|
+
from opentelemetry.baggage import get_all, set_baggage
|
|
23
|
+
from opentelemetry.context import attach, detach, get_current
|
|
24
|
+
from pydantic import BaseModel
|
|
34
25
|
|
|
35
26
|
log = get_module_logger(__name__)
|
|
36
27
|
|
|
@@ -88,11 +79,12 @@ class instrument: # pylint: disable=invalid-name
|
|
|
88
79
|
with tracing_context_manager(context=TracingContext.get()):
|
|
89
80
|
# debug_otel_context("[BEFORE STREAM] [BEFORE SETUP]")
|
|
90
81
|
|
|
91
|
-
captured_ctx = otel_context.get_current()
|
|
92
|
-
|
|
93
82
|
self._parse_type_and_kind()
|
|
94
83
|
|
|
95
|
-
self._attach_baggage()
|
|
84
|
+
baggage_token = self._attach_baggage()
|
|
85
|
+
|
|
86
|
+
# Capture AFTER baggage attach so we do not wipe it later.
|
|
87
|
+
captured_ctx = otel_context.get_current()
|
|
96
88
|
|
|
97
89
|
ctx = self._get_traceparent()
|
|
98
90
|
|
|
@@ -141,6 +133,7 @@ class instrument: # pylint: disable=invalid-name
|
|
|
141
133
|
otel_context.detach(otel_token)
|
|
142
134
|
|
|
143
135
|
# debug_otel_context("[WITHIN STREAM] [AFTER DETACH]")
|
|
136
|
+
self._detach_baggage(baggage_token)
|
|
144
137
|
|
|
145
138
|
return wrapped_generator()
|
|
146
139
|
|
|
@@ -311,15 +304,43 @@ class instrument: # pylint: disable=invalid-name
|
|
|
311
304
|
|
|
312
305
|
def _attach_baggage(self):
|
|
313
306
|
context = TracingContext.get()
|
|
307
|
+
otel_ctx = get_current()
|
|
308
|
+
|
|
309
|
+
# 1. Propagate any incoming `ag.*` baggage as-is (for example
|
|
310
|
+
# `ag.meta.session_id`) so all nested spans inherit it.
|
|
311
|
+
if context.baggage:
|
|
312
|
+
for k, v in context.baggage.items():
|
|
313
|
+
if not isinstance(k, str) or not k.startswith("ag."):
|
|
314
|
+
continue
|
|
315
|
+
if v is None:
|
|
316
|
+
continue
|
|
317
|
+
otel_ctx = set_baggage(name=k, value=str(v), context=otel_ctx)
|
|
318
|
+
|
|
319
|
+
# 2. Propagate Agenta references in baggage (used for linking traces to
|
|
320
|
+
# application/variant/environment).
|
|
321
|
+
if context.references:
|
|
322
|
+
for k, v in context.references.items():
|
|
323
|
+
if v is None:
|
|
324
|
+
continue
|
|
325
|
+
if isinstance(v, BaseModel):
|
|
326
|
+
try:
|
|
327
|
+
v = v.model_dump(mode="json", exclude_none=True)
|
|
328
|
+
except Exception: # pylint: disable=bare-except
|
|
329
|
+
pass
|
|
330
|
+
if isinstance(v, dict):
|
|
331
|
+
for field, value in v.items():
|
|
332
|
+
otel_ctx = set_baggage(
|
|
333
|
+
name=f"ag.refs.{k}.{field}",
|
|
334
|
+
value=str(value),
|
|
335
|
+
context=otel_ctx,
|
|
336
|
+
)
|
|
337
|
+
continue
|
|
338
|
+
otel_ctx = set_baggage(
|
|
339
|
+
name=f"ag.refs.{k}", value=str(v), context=otel_ctx
|
|
340
|
+
)
|
|
314
341
|
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
token = None
|
|
318
|
-
if references:
|
|
319
|
-
for k, v in references.items():
|
|
320
|
-
token = attach(baggage.set_baggage(f"ag.refs.{k}", v))
|
|
321
|
-
|
|
322
|
-
return token
|
|
342
|
+
# Attach once so we can reliably detach later.
|
|
343
|
+
return attach(otel_ctx)
|
|
323
344
|
|
|
324
345
|
def _detach_baggage(
|
|
325
346
|
self,
|
|
@@ -1,18 +1,17 @@
|
|
|
1
|
-
from typing import Optional, Dict, List
|
|
2
1
|
from threading import Lock
|
|
2
|
+
from typing import Dict, List, Optional
|
|
3
3
|
|
|
4
|
+
from agenta.sdk.models.tracing import BaseModel
|
|
5
|
+
from agenta.sdk.utils.logging import get_module_logger
|
|
4
6
|
from opentelemetry.baggage import get_all as get_baggage
|
|
5
7
|
from opentelemetry.context import Context
|
|
6
8
|
from opentelemetry.sdk.trace import Span, SpanProcessor
|
|
7
9
|
from opentelemetry.sdk.trace.export import (
|
|
8
|
-
SpanExporter,
|
|
9
|
-
ReadableSpan,
|
|
10
10
|
BatchSpanProcessor,
|
|
11
|
+
ReadableSpan,
|
|
12
|
+
SpanExporter,
|
|
11
13
|
)
|
|
12
14
|
|
|
13
|
-
from agenta.sdk.utils.logging import get_module_logger
|
|
14
|
-
from agenta.sdk.engines.tracing.conventions import Reference
|
|
15
|
-
|
|
16
15
|
log = get_module_logger(__name__)
|
|
17
16
|
|
|
18
17
|
|
|
@@ -51,15 +50,27 @@ class TraceProcessor(SpanProcessor):
|
|
|
51
50
|
parent_context: Optional[Context] = None,
|
|
52
51
|
) -> None:
|
|
53
52
|
for key in self.references.keys():
|
|
54
|
-
|
|
53
|
+
ref = self.references[key]
|
|
54
|
+
if ref is None:
|
|
55
|
+
continue
|
|
56
|
+
if isinstance(ref, BaseModel):
|
|
57
|
+
try:
|
|
58
|
+
ref = ref.model_dump(mode="json", exclude_none=True)
|
|
59
|
+
except Exception: # pylint: disable=bare-except
|
|
60
|
+
pass
|
|
61
|
+
if isinstance(ref, dict):
|
|
62
|
+
for field, value in ref.items():
|
|
63
|
+
span.set_attribute(f"ag.refs.{key}.{field}", str(value))
|
|
64
|
+
else:
|
|
65
|
+
span.set_attribute(f"ag.refs.{key}", str(ref))
|
|
55
66
|
|
|
56
67
|
baggage = get_baggage(parent_context)
|
|
57
68
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
69
|
+
# Copy any `ag.*` baggage entries onto the span attributes so they can be
|
|
70
|
+
# used for filtering and grouping (for example `ag.meta.session_id`).
|
|
71
|
+
for key, value in baggage.items():
|
|
72
|
+
if key.startswith("ag."):
|
|
73
|
+
span.set_attribute(key, value)
|
|
63
74
|
|
|
64
75
|
trace_id = span.context.trace_id
|
|
65
76
|
span_id = span.context.span_id
|