agentmark-sdk 0.1.0__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.
- agentmark_sdk/__init__.py +71 -0
- agentmark_sdk/config.py +12 -0
- agentmark_sdk/decorator.py +182 -0
- agentmark_sdk/sampler.py +60 -0
- agentmark_sdk/sdk.py +252 -0
- agentmark_sdk/serialize.py +33 -0
- agentmark_sdk/trace.py +331 -0
- agentmark_sdk-0.1.0.dist-info/METADATA +24 -0
- agentmark_sdk-0.1.0.dist-info/RECORD +10 -0
- agentmark_sdk-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""AgentMark SDK for Python.
|
|
2
|
+
|
|
3
|
+
Provides OpenTelemetry-based tracing and observability for AI applications.
|
|
4
|
+
|
|
5
|
+
Example:
|
|
6
|
+
from agentmark_sdk import AgentMarkSDK, span, SpanOptions
|
|
7
|
+
|
|
8
|
+
# Initialize the SDK
|
|
9
|
+
sdk = AgentMarkSDK(api_key="sk-...", app_id="app_123")
|
|
10
|
+
sdk.init_tracing()
|
|
11
|
+
|
|
12
|
+
# Create a span around an operation
|
|
13
|
+
result = await span(
|
|
14
|
+
SpanOptions(name="my-operation", user_id="user-1"),
|
|
15
|
+
my_async_function,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
# Auto-capture IO with decorator
|
|
19
|
+
from agentmark_sdk import observe, SpanKind
|
|
20
|
+
|
|
21
|
+
@observe(kind=SpanKind.TOOL)
|
|
22
|
+
async def my_tool(query: str) -> dict:
|
|
23
|
+
return {"result": "data"}
|
|
24
|
+
|
|
25
|
+
# Submit a score
|
|
26
|
+
await sdk.score(
|
|
27
|
+
resource_id=result.trace_id,
|
|
28
|
+
name="accuracy",
|
|
29
|
+
score=0.95,
|
|
30
|
+
)
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
from .config import (
|
|
34
|
+
AGENTMARK_KEY,
|
|
35
|
+
AGENTMARK_SCORE_ENDPOINT,
|
|
36
|
+
AGENTMARK_TRACE_ENDPOINT,
|
|
37
|
+
DEFAULT_BASE_URL,
|
|
38
|
+
METADATA_KEY,
|
|
39
|
+
)
|
|
40
|
+
from .decorator import SpanKind, observe
|
|
41
|
+
from .sampler import AgentmarkSampler
|
|
42
|
+
from .sdk import AgentMarkSDK
|
|
43
|
+
from .serialize import serialize_value
|
|
44
|
+
from .trace import SpanContext, SpanOptions, SpanResult, span, span_context, span_context_sync
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
# SDK
|
|
48
|
+
"AgentMarkSDK",
|
|
49
|
+
# Span utilities
|
|
50
|
+
"span",
|
|
51
|
+
"span_context",
|
|
52
|
+
"span_context_sync",
|
|
53
|
+
"observe",
|
|
54
|
+
"SpanOptions",
|
|
55
|
+
"SpanContext",
|
|
56
|
+
"SpanResult",
|
|
57
|
+
# Span kinds
|
|
58
|
+
"SpanKind",
|
|
59
|
+
# Serialization
|
|
60
|
+
"serialize_value",
|
|
61
|
+
# Sampler
|
|
62
|
+
"AgentmarkSampler",
|
|
63
|
+
# Config
|
|
64
|
+
"AGENTMARK_KEY",
|
|
65
|
+
"METADATA_KEY",
|
|
66
|
+
"AGENTMARK_TRACE_ENDPOINT",
|
|
67
|
+
"AGENTMARK_SCORE_ENDPOINT",
|
|
68
|
+
"DEFAULT_BASE_URL",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
__version__ = "0.1.0"
|
agentmark_sdk/config.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Configuration constants for AgentMark SDK."""
|
|
2
|
+
|
|
3
|
+
# API Endpoints
|
|
4
|
+
AGENTMARK_TRACE_ENDPOINT = "v1/traces"
|
|
5
|
+
AGENTMARK_SCORE_ENDPOINT = "v1/score"
|
|
6
|
+
|
|
7
|
+
# Default base URL
|
|
8
|
+
DEFAULT_BASE_URL = "https://api.agentmark.co"
|
|
9
|
+
|
|
10
|
+
# Span attribute prefixes
|
|
11
|
+
AGENTMARK_KEY = "agentmark"
|
|
12
|
+
METADATA_KEY = "agentmark.metadata"
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
"""Decorator-based tracing for automatic IO capture.
|
|
2
|
+
|
|
3
|
+
Provides the @observe decorator that auto-captures function arguments
|
|
4
|
+
as span input and return values as span output.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import functools
|
|
10
|
+
import inspect
|
|
11
|
+
from enum import Enum
|
|
12
|
+
from typing import Any, Callable, TypeVar, overload
|
|
13
|
+
|
|
14
|
+
from opentelemetry import trace as otel_trace
|
|
15
|
+
from opentelemetry.trace import StatusCode
|
|
16
|
+
|
|
17
|
+
from .config import AGENTMARK_KEY
|
|
18
|
+
from .serialize import serialize_value
|
|
19
|
+
|
|
20
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
21
|
+
|
|
22
|
+
# Attribute keys matching the gen_ai semantic conventions used by the adapter
|
|
23
|
+
INPUT_KEY = "gen_ai.request.input"
|
|
24
|
+
OUTPUT_KEY = "gen_ai.response.output"
|
|
25
|
+
SPAN_KIND_KEY = f"{AGENTMARK_KEY}.span.kind"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class SpanKind(str, Enum):
|
|
29
|
+
"""Span kind for categorizing observed operations."""
|
|
30
|
+
|
|
31
|
+
FUNCTION = "function"
|
|
32
|
+
LLM = "llm"
|
|
33
|
+
TOOL = "tool"
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _capture_inputs(
|
|
37
|
+
fn: Callable[..., Any],
|
|
38
|
+
args: tuple[Any, ...],
|
|
39
|
+
kwargs: dict[str, Any],
|
|
40
|
+
process_inputs: Callable[[dict[str, Any]], dict[str, Any]] | None,
|
|
41
|
+
) -> str | None:
|
|
42
|
+
"""Capture function arguments as a serialized input string."""
|
|
43
|
+
try:
|
|
44
|
+
sig = inspect.signature(fn)
|
|
45
|
+
bound = sig.bind(*args, **kwargs)
|
|
46
|
+
bound.apply_defaults()
|
|
47
|
+
inputs = dict(bound.arguments)
|
|
48
|
+
except (TypeError, ValueError):
|
|
49
|
+
# Fallback if signature binding fails
|
|
50
|
+
inputs = {"args": list(args), "kwargs": kwargs} if kwargs else {"args": list(args)}
|
|
51
|
+
|
|
52
|
+
if process_inputs is not None:
|
|
53
|
+
# Pass raw args (including self) to custom processor
|
|
54
|
+
inputs = process_inputs(inputs)
|
|
55
|
+
# Exclude self/cls for methods (after process_inputs so it can access self)
|
|
56
|
+
inputs.pop("self", None)
|
|
57
|
+
inputs.pop("cls", None)
|
|
58
|
+
|
|
59
|
+
return serialize_value(inputs)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _capture_output(
|
|
63
|
+
result: Any,
|
|
64
|
+
process_outputs: Callable[[Any], Any] | None,
|
|
65
|
+
) -> str | None:
|
|
66
|
+
"""Capture function return value as a serialized output string."""
|
|
67
|
+
output = result
|
|
68
|
+
if process_outputs is not None:
|
|
69
|
+
output = process_outputs(output)
|
|
70
|
+
return serialize_value(output)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@overload
|
|
74
|
+
def observe(_fn: F) -> F: ...
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@overload
|
|
78
|
+
def observe(
|
|
79
|
+
_fn: None = None,
|
|
80
|
+
*,
|
|
81
|
+
name: str | None = None,
|
|
82
|
+
kind: SpanKind = SpanKind.FUNCTION,
|
|
83
|
+
capture_input: bool = True,
|
|
84
|
+
capture_output: bool = True,
|
|
85
|
+
process_inputs: Callable[[dict[str, Any]], dict[str, Any]] | None = None,
|
|
86
|
+
process_outputs: Callable[[Any], Any] | None = None,
|
|
87
|
+
) -> Callable[[F], F]: ...
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def observe(
|
|
91
|
+
_fn: F | None = None,
|
|
92
|
+
*,
|
|
93
|
+
name: str | None = None,
|
|
94
|
+
kind: SpanKind = SpanKind.FUNCTION,
|
|
95
|
+
capture_input: bool = True,
|
|
96
|
+
capture_output: bool = True,
|
|
97
|
+
process_inputs: Callable[[dict[str, Any]], dict[str, Any]] | None = None,
|
|
98
|
+
process_outputs: Callable[[Any], Any] | None = None,
|
|
99
|
+
) -> F | Callable[[F], F]:
|
|
100
|
+
"""Decorator that auto-captures function IO as span attributes.
|
|
101
|
+
|
|
102
|
+
Supports both @observe and @observe() syntax, sync and async functions.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
name: Custom span name. Defaults to the function name.
|
|
106
|
+
kind: Span kind for categorization. Defaults to SpanKind.FUNCTION.
|
|
107
|
+
capture_input: Whether to capture function args as input. Default True.
|
|
108
|
+
capture_output: Whether to capture return value as output. Default True.
|
|
109
|
+
process_inputs: Optional transform applied to inputs before serialization.
|
|
110
|
+
process_outputs: Optional transform applied to output before serialization.
|
|
111
|
+
|
|
112
|
+
Example:
|
|
113
|
+
@observe
|
|
114
|
+
async def my_function(item_type: str) -> dict:
|
|
115
|
+
return {"result": "data"}
|
|
116
|
+
|
|
117
|
+
@observe(name="custom-name", kind=SpanKind.TOOL)
|
|
118
|
+
async def call_api(query: str) -> dict:
|
|
119
|
+
...
|
|
120
|
+
|
|
121
|
+
@observe(process_inputs=lambda inputs: {k: v for k, v in inputs.items() if k != "api_key"})
|
|
122
|
+
async def call_api(api_key: str, query: str) -> dict:
|
|
123
|
+
...
|
|
124
|
+
"""
|
|
125
|
+
|
|
126
|
+
def decorator(fn: F) -> F:
|
|
127
|
+
span_name = name or fn.__name__
|
|
128
|
+
|
|
129
|
+
@functools.wraps(fn)
|
|
130
|
+
async def async_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
131
|
+
tracer = otel_trace.get_tracer("agentmark")
|
|
132
|
+
with tracer.start_as_current_span(span_name) as span:
|
|
133
|
+
span.set_attribute(SPAN_KIND_KEY, kind.value)
|
|
134
|
+
|
|
135
|
+
if capture_input:
|
|
136
|
+
input_str = _capture_inputs(fn, args, kwargs, process_inputs)
|
|
137
|
+
if input_str is not None:
|
|
138
|
+
span.set_attribute(INPUT_KEY, input_str)
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
result = await fn(*args, **kwargs)
|
|
142
|
+
if capture_output:
|
|
143
|
+
output_str = _capture_output(result, process_outputs)
|
|
144
|
+
if output_str is not None:
|
|
145
|
+
span.set_attribute(OUTPUT_KEY, output_str)
|
|
146
|
+
span.set_status(StatusCode.OK)
|
|
147
|
+
return result
|
|
148
|
+
except Exception as e:
|
|
149
|
+
span.set_status(StatusCode.ERROR, str(e))
|
|
150
|
+
raise
|
|
151
|
+
|
|
152
|
+
@functools.wraps(fn)
|
|
153
|
+
def sync_wrapper(*args: Any, **kwargs: Any) -> Any:
|
|
154
|
+
tracer = otel_trace.get_tracer("agentmark")
|
|
155
|
+
with tracer.start_as_current_span(span_name) as span:
|
|
156
|
+
span.set_attribute(SPAN_KIND_KEY, kind.value)
|
|
157
|
+
|
|
158
|
+
if capture_input:
|
|
159
|
+
input_str = _capture_inputs(fn, args, kwargs, process_inputs)
|
|
160
|
+
if input_str is not None:
|
|
161
|
+
span.set_attribute(INPUT_KEY, input_str)
|
|
162
|
+
|
|
163
|
+
try:
|
|
164
|
+
result = fn(*args, **kwargs)
|
|
165
|
+
if capture_output:
|
|
166
|
+
output_str = _capture_output(result, process_outputs)
|
|
167
|
+
if output_str is not None:
|
|
168
|
+
span.set_attribute(OUTPUT_KEY, output_str)
|
|
169
|
+
span.set_status(StatusCode.OK)
|
|
170
|
+
return result
|
|
171
|
+
except Exception as e:
|
|
172
|
+
span.set_status(StatusCode.ERROR, str(e))
|
|
173
|
+
raise
|
|
174
|
+
|
|
175
|
+
if inspect.iscoroutinefunction(fn):
|
|
176
|
+
return async_wrapper # type: ignore[return-value]
|
|
177
|
+
return sync_wrapper # type: ignore[return-value]
|
|
178
|
+
|
|
179
|
+
# Support both @observe and @observe() syntax
|
|
180
|
+
if _fn is not None:
|
|
181
|
+
return decorator(_fn)
|
|
182
|
+
return decorator # type: ignore[return-value]
|
agentmark_sdk/sampler.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Custom sampler for AgentMark traces."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING, Sequence
|
|
6
|
+
|
|
7
|
+
from opentelemetry.context import Context
|
|
8
|
+
from opentelemetry.sdk.trace.sampling import (
|
|
9
|
+
Decision,
|
|
10
|
+
Sampler,
|
|
11
|
+
SamplingResult,
|
|
12
|
+
)
|
|
13
|
+
from opentelemetry.trace import Link, SpanKind
|
|
14
|
+
from opentelemetry.util.types import Attributes
|
|
15
|
+
|
|
16
|
+
# Span attributes that indicate internal framework spans to filter out
|
|
17
|
+
FILTERED_ATTRIBUTE_KEYS = ["next.span_name", "next.clientComponentLoadCount"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class AgentmarkSampler(Sampler):
|
|
21
|
+
"""Custom sampler that filters out internal framework spans.
|
|
22
|
+
|
|
23
|
+
This sampler drops spans that have attributes indicating they are
|
|
24
|
+
internal framework spans (e.g., Next.js internals) that would add
|
|
25
|
+
noise to traces without providing useful debugging information.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def should_sample(
|
|
29
|
+
self,
|
|
30
|
+
parent_context: Context | None,
|
|
31
|
+
trace_id: int,
|
|
32
|
+
name: str,
|
|
33
|
+
kind: SpanKind | None = None,
|
|
34
|
+
attributes: Attributes | None = None,
|
|
35
|
+
links: Sequence[Link] | None = None,
|
|
36
|
+
) -> SamplingResult:
|
|
37
|
+
"""Determine whether to sample this span.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
parent_context: The parent context (if any).
|
|
41
|
+
trace_id: The trace ID.
|
|
42
|
+
name: The span name.
|
|
43
|
+
kind: The span kind.
|
|
44
|
+
attributes: Span attributes.
|
|
45
|
+
links: Span links.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
SamplingResult indicating whether to record/sample.
|
|
49
|
+
"""
|
|
50
|
+
# Check if any filtered attribute keys are present
|
|
51
|
+
if attributes:
|
|
52
|
+
for key in FILTERED_ATTRIBUTE_KEYS:
|
|
53
|
+
if key in attributes:
|
|
54
|
+
return SamplingResult(Decision.DROP)
|
|
55
|
+
|
|
56
|
+
return SamplingResult(Decision.RECORD_AND_SAMPLE)
|
|
57
|
+
|
|
58
|
+
def get_description(self) -> str:
|
|
59
|
+
"""Return a description of this sampler."""
|
|
60
|
+
return "AgentmarkSampler"
|
agentmark_sdk/sdk.py
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""AgentMark SDK main class."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
import httpx
|
|
8
|
+
from opentelemetry import trace as otel_trace
|
|
9
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
|
|
10
|
+
from opentelemetry.sdk.resources import Resource
|
|
11
|
+
from opentelemetry.sdk.trace import TracerProvider
|
|
12
|
+
from opentelemetry.sdk.trace.export import BatchSpanProcessor, SimpleSpanProcessor
|
|
13
|
+
|
|
14
|
+
from .config import (
|
|
15
|
+
AGENTMARK_SCORE_ENDPOINT,
|
|
16
|
+
AGENTMARK_TRACE_ENDPOINT,
|
|
17
|
+
DEFAULT_BASE_URL,
|
|
18
|
+
)
|
|
19
|
+
from .sampler import AgentmarkSampler
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AgentMarkSDK:
|
|
23
|
+
"""AgentMark SDK for Python.
|
|
24
|
+
|
|
25
|
+
Provides OpenTelemetry tracing initialization and score submission.
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
sdk = AgentMarkSDK(api_key="sk-...", app_id="app_123")
|
|
29
|
+
sdk.init_tracing()
|
|
30
|
+
|
|
31
|
+
# Use span() function from the SDK
|
|
32
|
+
from agentmark_sdk import span, SpanOptions
|
|
33
|
+
result = await span(
|
|
34
|
+
SpanOptions(name="my-operation"),
|
|
35
|
+
my_async_function,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# Submit a score
|
|
39
|
+
await sdk.score(
|
|
40
|
+
resource_id=result.trace_id,
|
|
41
|
+
name="accuracy",
|
|
42
|
+
score=0.95,
|
|
43
|
+
label="good",
|
|
44
|
+
reason="Response matched expected output",
|
|
45
|
+
)
|
|
46
|
+
"""
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
api_key: str,
|
|
51
|
+
app_id: str,
|
|
52
|
+
base_url: str = DEFAULT_BASE_URL,
|
|
53
|
+
) -> None:
|
|
54
|
+
"""Initialize the SDK.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
api_key: AgentMark API key.
|
|
58
|
+
app_id: AgentMark application ID.
|
|
59
|
+
base_url: Base URL for the AgentMark API.
|
|
60
|
+
"""
|
|
61
|
+
self._api_key = api_key
|
|
62
|
+
self._app_id = app_id
|
|
63
|
+
self._base_url = base_url.rstrip("/")
|
|
64
|
+
self._tracer_provider: TracerProvider | None = None
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def api_key(self) -> str:
|
|
68
|
+
"""Get the API key."""
|
|
69
|
+
return self._api_key
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def app_id(self) -> str:
|
|
73
|
+
"""Get the application ID."""
|
|
74
|
+
return self._app_id
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def base_url(self) -> str:
|
|
78
|
+
"""Get the base URL."""
|
|
79
|
+
return self._base_url
|
|
80
|
+
|
|
81
|
+
def init_tracing(self, disable_batch: bool = False) -> TracerProvider:
|
|
82
|
+
"""Initialize OpenTelemetry tracing with AgentMark exporter.
|
|
83
|
+
|
|
84
|
+
Call this once at application startup.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
disable_batch: If True, use SimpleSpanProcessor (immediate export).
|
|
88
|
+
If False (default), use BatchSpanProcessor for better performance.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
The configured TracerProvider.
|
|
92
|
+
|
|
93
|
+
Example:
|
|
94
|
+
sdk = AgentMarkSDK(api_key="sk-...", app_id="app_123")
|
|
95
|
+
provider = sdk.init_tracing()
|
|
96
|
+
"""
|
|
97
|
+
exporter_url = f"{self._base_url}/{AGENTMARK_TRACE_ENDPOINT}"
|
|
98
|
+
|
|
99
|
+
exporter = OTLPSpanExporter(
|
|
100
|
+
endpoint=exporter_url,
|
|
101
|
+
headers={
|
|
102
|
+
"Authorization": self._api_key,
|
|
103
|
+
"X-Agentmark-App-Id": self._app_id,
|
|
104
|
+
},
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
processor: SimpleSpanProcessor | BatchSpanProcessor
|
|
108
|
+
if disable_batch:
|
|
109
|
+
processor = SimpleSpanProcessor(exporter)
|
|
110
|
+
else:
|
|
111
|
+
processor = BatchSpanProcessor(exporter)
|
|
112
|
+
|
|
113
|
+
resource = Resource.create(
|
|
114
|
+
{
|
|
115
|
+
"service.name": "agentmark-client",
|
|
116
|
+
"agentmark.app_id": self._app_id,
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
provider = TracerProvider(
|
|
121
|
+
resource=resource,
|
|
122
|
+
sampler=AgentmarkSampler(),
|
|
123
|
+
)
|
|
124
|
+
provider.add_span_processor(processor)
|
|
125
|
+
|
|
126
|
+
otel_trace.set_tracer_provider(provider)
|
|
127
|
+
self._tracer_provider = provider
|
|
128
|
+
|
|
129
|
+
return provider
|
|
130
|
+
|
|
131
|
+
async def score(
|
|
132
|
+
self,
|
|
133
|
+
resource_id: str,
|
|
134
|
+
name: str,
|
|
135
|
+
score: float,
|
|
136
|
+
label: str | None = None,
|
|
137
|
+
reason: str | None = None,
|
|
138
|
+
type: str | None = None,
|
|
139
|
+
) -> dict[str, Any]:
|
|
140
|
+
"""Submit a score for a trace/span.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
resource_id: The trace or span ID to score.
|
|
144
|
+
name: Name of the score metric (e.g., "accuracy", "relevance").
|
|
145
|
+
score: Numeric score value (typically 0.0-1.0).
|
|
146
|
+
label: Optional label (e.g., "good", "bad", "neutral").
|
|
147
|
+
reason: Optional explanation for the score.
|
|
148
|
+
type: Optional score type identifier.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
Response data from the API.
|
|
152
|
+
|
|
153
|
+
Raises:
|
|
154
|
+
Exception: If the API request fails.
|
|
155
|
+
|
|
156
|
+
Example:
|
|
157
|
+
await sdk.score(
|
|
158
|
+
resource_id="abc123",
|
|
159
|
+
name="accuracy",
|
|
160
|
+
score=0.95,
|
|
161
|
+
label="good",
|
|
162
|
+
reason="Response matched expected",
|
|
163
|
+
)
|
|
164
|
+
"""
|
|
165
|
+
url = f"{self._base_url}/{AGENTMARK_SCORE_ENDPOINT}"
|
|
166
|
+
|
|
167
|
+
payload: dict[str, Any] = {
|
|
168
|
+
"resourceId": resource_id,
|
|
169
|
+
"name": name,
|
|
170
|
+
"score": score,
|
|
171
|
+
}
|
|
172
|
+
if label is not None:
|
|
173
|
+
payload["label"] = label
|
|
174
|
+
if reason is not None:
|
|
175
|
+
payload["reason"] = reason
|
|
176
|
+
if type is not None:
|
|
177
|
+
payload["type"] = type
|
|
178
|
+
|
|
179
|
+
async with httpx.AsyncClient() as client:
|
|
180
|
+
response = await client.post(
|
|
181
|
+
url,
|
|
182
|
+
json=payload,
|
|
183
|
+
headers={
|
|
184
|
+
"Content-Type": "application/json",
|
|
185
|
+
"Authorization": self._api_key,
|
|
186
|
+
"X-Agentmark-App-Id": self._app_id,
|
|
187
|
+
},
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
if response.is_success:
|
|
191
|
+
data = response.json()
|
|
192
|
+
return data.get("data", {})
|
|
193
|
+
|
|
194
|
+
error_data = response.json()
|
|
195
|
+
raise Exception(error_data.get("error", "Unknown error"))
|
|
196
|
+
|
|
197
|
+
def score_sync(
|
|
198
|
+
self,
|
|
199
|
+
resource_id: str,
|
|
200
|
+
name: str,
|
|
201
|
+
score: float,
|
|
202
|
+
label: str | None = None,
|
|
203
|
+
reason: str | None = None,
|
|
204
|
+
type: str | None = None,
|
|
205
|
+
) -> dict[str, Any]:
|
|
206
|
+
"""Submit a score for a trace/span (synchronous version).
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
resource_id: The trace or span ID to score.
|
|
210
|
+
name: Name of the score metric.
|
|
211
|
+
score: Numeric score value.
|
|
212
|
+
label: Optional label.
|
|
213
|
+
reason: Optional explanation.
|
|
214
|
+
type: Optional score type.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Response data from the API.
|
|
218
|
+
|
|
219
|
+
Raises:
|
|
220
|
+
Exception: If the API request fails.
|
|
221
|
+
"""
|
|
222
|
+
url = f"{self._base_url}/{AGENTMARK_SCORE_ENDPOINT}"
|
|
223
|
+
|
|
224
|
+
payload: dict[str, Any] = {
|
|
225
|
+
"resourceId": resource_id,
|
|
226
|
+
"name": name,
|
|
227
|
+
"score": score,
|
|
228
|
+
}
|
|
229
|
+
if label is not None:
|
|
230
|
+
payload["label"] = label
|
|
231
|
+
if reason is not None:
|
|
232
|
+
payload["reason"] = reason
|
|
233
|
+
if type is not None:
|
|
234
|
+
payload["type"] = type
|
|
235
|
+
|
|
236
|
+
with httpx.Client() as client:
|
|
237
|
+
response = client.post(
|
|
238
|
+
url,
|
|
239
|
+
json=payload,
|
|
240
|
+
headers={
|
|
241
|
+
"Content-Type": "application/json",
|
|
242
|
+
"Authorization": self._api_key,
|
|
243
|
+
"X-Agentmark-App-Id": self._app_id,
|
|
244
|
+
},
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
if response.is_success:
|
|
248
|
+
data = response.json()
|
|
249
|
+
return data.get("data", {})
|
|
250
|
+
|
|
251
|
+
error_data = response.json()
|
|
252
|
+
raise Exception(error_data.get("error", "Unknown error"))
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Serialization utilities for observed function IO capture."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import dataclasses
|
|
6
|
+
import json
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
MAX_SERIALIZE_LENGTH = 1_000_000
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def serialize_value(value: Any, max_length: int = MAX_SERIALIZE_LENGTH) -> str:
|
|
13
|
+
"""Serialize a value to a JSON string for span attributes.
|
|
14
|
+
|
|
15
|
+
Serialization chain:
|
|
16
|
+
1. Pydantic model → .model_dump() → JSON
|
|
17
|
+
2. Dataclass → dataclasses.asdict() → JSON
|
|
18
|
+
3. Dict/list/primitive → JSON directly
|
|
19
|
+
4. Fallback → str(obj)
|
|
20
|
+
|
|
21
|
+
Truncated to max_length characters.
|
|
22
|
+
"""
|
|
23
|
+
try:
|
|
24
|
+
if hasattr(value, "model_dump"):
|
|
25
|
+
serialized = json.dumps(value.model_dump(), default=str)
|
|
26
|
+
elif dataclasses.is_dataclass(value) and not isinstance(value, type):
|
|
27
|
+
serialized = json.dumps(dataclasses.asdict(value), default=str)
|
|
28
|
+
else:
|
|
29
|
+
serialized = json.dumps(value, default=str)
|
|
30
|
+
except (TypeError, ValueError, OverflowError):
|
|
31
|
+
serialized = str(value)
|
|
32
|
+
|
|
33
|
+
return serialized[:max_length]
|
agentmark_sdk/trace.py
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"""Tracing utilities for AgentMark SDK.
|
|
2
|
+
|
|
3
|
+
Provides the trace() function for wrapping operations with OpenTelemetry spans.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from contextlib import asynccontextmanager
|
|
9
|
+
from dataclasses import dataclass, field
|
|
10
|
+
from typing import Any, AsyncIterator, Callable, Generic, TypeVar
|
|
11
|
+
|
|
12
|
+
from opentelemetry import trace as otel_trace
|
|
13
|
+
from opentelemetry.trace import Span, SpanKind, StatusCode, Tracer
|
|
14
|
+
from opentelemetry.util.types import Attributes
|
|
15
|
+
|
|
16
|
+
from .config import AGENTMARK_KEY, METADATA_KEY
|
|
17
|
+
|
|
18
|
+
T = TypeVar("T")
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class TraceOptions:
|
|
23
|
+
"""Options for creating a trace.
|
|
24
|
+
|
|
25
|
+
Attributes:
|
|
26
|
+
name: Name of the trace/span.
|
|
27
|
+
metadata: Additional metadata key-value pairs.
|
|
28
|
+
session_id: Session identifier for grouping traces.
|
|
29
|
+
session_name: Human-readable session name.
|
|
30
|
+
user_id: User identifier.
|
|
31
|
+
dataset_run_id: Dataset run identifier (for experiments).
|
|
32
|
+
dataset_run_name: Dataset run name (for experiments).
|
|
33
|
+
dataset_item_name: Dataset item name (for experiments).
|
|
34
|
+
dataset_expected_output: Expected output for dataset item.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
name: str
|
|
38
|
+
metadata: dict[str, str] | None = None
|
|
39
|
+
session_id: str | None = None
|
|
40
|
+
session_name: str | None = None
|
|
41
|
+
user_id: str | None = None
|
|
42
|
+
prompt_name: str | None = None
|
|
43
|
+
dataset_run_id: str | None = None
|
|
44
|
+
dataset_run_name: str | None = None
|
|
45
|
+
dataset_item_name: str | None = None
|
|
46
|
+
dataset_expected_output: str | None = None
|
|
47
|
+
dataset_input: str | None = None
|
|
48
|
+
dataset_path: str | None = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@dataclass
|
|
52
|
+
class TraceContext:
|
|
53
|
+
"""Context passed to traced functions.
|
|
54
|
+
|
|
55
|
+
Provides access to trace information and methods for adding
|
|
56
|
+
attributes, events, and child spans.
|
|
57
|
+
|
|
58
|
+
Attributes:
|
|
59
|
+
trace_id: The trace ID in hex format.
|
|
60
|
+
span_id: The span ID in hex format.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
trace_id: str
|
|
64
|
+
span_id: str
|
|
65
|
+
_span: Span = field(repr=False)
|
|
66
|
+
_tracer: Tracer = field(repr=False)
|
|
67
|
+
|
|
68
|
+
def set_attribute(self, key: str, value: str | int | float | bool) -> None:
|
|
69
|
+
"""Set an attribute on this span.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
key: Attribute key.
|
|
73
|
+
value: Attribute value.
|
|
74
|
+
"""
|
|
75
|
+
self._span.set_attribute(key, value)
|
|
76
|
+
|
|
77
|
+
def set_input(self, data: dict[str, Any]) -> None:
|
|
78
|
+
"""Record input data on this span."""
|
|
79
|
+
from .serialize import serialize_value
|
|
80
|
+
|
|
81
|
+
self._span.set_attribute(
|
|
82
|
+
f"{AGENTMARK_KEY}.input", serialize_value(data),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def set_output(self, data: dict[str, Any]) -> None:
|
|
86
|
+
"""Record output data on this span."""
|
|
87
|
+
from .serialize import serialize_value
|
|
88
|
+
|
|
89
|
+
self._span.set_attribute(
|
|
90
|
+
f"{AGENTMARK_KEY}.output", serialize_value(data),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
def add_event(
|
|
94
|
+
self, name: str, attributes: dict[str, Any] | None = None
|
|
95
|
+
) -> None:
|
|
96
|
+
"""Add an event to this span.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
name: Event name.
|
|
100
|
+
attributes: Optional event attributes.
|
|
101
|
+
"""
|
|
102
|
+
self._span.add_event(name, attributes or {})
|
|
103
|
+
|
|
104
|
+
@asynccontextmanager
|
|
105
|
+
async def span(
|
|
106
|
+
self, name: str, metadata: dict[str, str] | None = None
|
|
107
|
+
) -> AsyncIterator[TraceContext]:
|
|
108
|
+
"""Create a child span within this trace.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
name: Child span name.
|
|
112
|
+
metadata: Optional metadata for the child span.
|
|
113
|
+
|
|
114
|
+
Yields:
|
|
115
|
+
TraceContext for the child span.
|
|
116
|
+
"""
|
|
117
|
+
with self._tracer.start_as_current_span(name) as child_span:
|
|
118
|
+
if metadata:
|
|
119
|
+
for key, value in metadata.items():
|
|
120
|
+
child_span.set_attribute(f"{METADATA_KEY}.{key}", value)
|
|
121
|
+
|
|
122
|
+
span_ctx = child_span.get_span_context()
|
|
123
|
+
child_ctx = TraceContext(
|
|
124
|
+
trace_id=self.trace_id, # Same trace ID
|
|
125
|
+
span_id=format(span_ctx.span_id, "016x"),
|
|
126
|
+
_span=child_span,
|
|
127
|
+
_tracer=self._tracer,
|
|
128
|
+
)
|
|
129
|
+
try:
|
|
130
|
+
yield child_ctx
|
|
131
|
+
child_span.set_status(StatusCode.OK)
|
|
132
|
+
except Exception as e:
|
|
133
|
+
child_span.set_status(StatusCode.ERROR, str(e))
|
|
134
|
+
raise
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@dataclass
|
|
138
|
+
class TraceResult(Generic[T]):
|
|
139
|
+
"""Result from trace execution.
|
|
140
|
+
|
|
141
|
+
Attributes:
|
|
142
|
+
result: The result of the traced function.
|
|
143
|
+
trace_id: The trace ID for correlation.
|
|
144
|
+
"""
|
|
145
|
+
|
|
146
|
+
result: T
|
|
147
|
+
trace_id: str
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _set_agentmark_attributes(span: Span, options: TraceOptions) -> None:
|
|
151
|
+
"""Set AgentMark-specific attributes on a span.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
span: The span to set attributes on.
|
|
155
|
+
options: Trace options containing attribute values.
|
|
156
|
+
"""
|
|
157
|
+
span.set_attribute(f"{AGENTMARK_KEY}.trace_name", options.name)
|
|
158
|
+
|
|
159
|
+
if options.session_id:
|
|
160
|
+
span.set_attribute(f"{AGENTMARK_KEY}.session_id", options.session_id)
|
|
161
|
+
if options.session_name:
|
|
162
|
+
span.set_attribute(f"{AGENTMARK_KEY}.session_name", options.session_name)
|
|
163
|
+
if options.user_id:
|
|
164
|
+
span.set_attribute(f"{AGENTMARK_KEY}.user_id", options.user_id)
|
|
165
|
+
if options.prompt_name:
|
|
166
|
+
span.set_attribute(f"{AGENTMARK_KEY}.prompt_name", options.prompt_name)
|
|
167
|
+
if options.dataset_run_id:
|
|
168
|
+
span.set_attribute(f"{AGENTMARK_KEY}.dataset_run_id", options.dataset_run_id)
|
|
169
|
+
if options.dataset_run_name:
|
|
170
|
+
span.set_attribute(f"{AGENTMARK_KEY}.dataset_run_name", options.dataset_run_name)
|
|
171
|
+
if options.dataset_item_name:
|
|
172
|
+
span.set_attribute(f"{AGENTMARK_KEY}.dataset_item_name", options.dataset_item_name)
|
|
173
|
+
if options.dataset_expected_output:
|
|
174
|
+
span.set_attribute(
|
|
175
|
+
f"{AGENTMARK_KEY}.dataset_expected_output", options.dataset_expected_output
|
|
176
|
+
)
|
|
177
|
+
if options.dataset_input:
|
|
178
|
+
span.set_attribute(f"{AGENTMARK_KEY}.dataset_input", options.dataset_input)
|
|
179
|
+
if options.dataset_path:
|
|
180
|
+
span.set_attribute(f"{AGENTMARK_KEY}.dataset_path", options.dataset_path)
|
|
181
|
+
|
|
182
|
+
if options.metadata:
|
|
183
|
+
for key, value in options.metadata.items():
|
|
184
|
+
span.set_attribute(f"{METADATA_KEY}.{key}", value)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
async def trace(
|
|
188
|
+
options: TraceOptions | str,
|
|
189
|
+
fn: Callable[..., Any],
|
|
190
|
+
*args: Any,
|
|
191
|
+
**kwargs: Any,
|
|
192
|
+
) -> TraceResult[Any]:
|
|
193
|
+
"""Start a new trace and execute a function within it.
|
|
194
|
+
|
|
195
|
+
Creates a root span, executes the provided function, and returns
|
|
196
|
+
both the result and the trace ID.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
options: Trace options or just a name string.
|
|
200
|
+
fn: The async function to execute within the trace.
|
|
201
|
+
*args: Positional arguments to pass to the function.
|
|
202
|
+
**kwargs: Keyword arguments to pass to the function.
|
|
203
|
+
|
|
204
|
+
Returns:
|
|
205
|
+
TraceResult containing the function result and trace ID.
|
|
206
|
+
|
|
207
|
+
Example:
|
|
208
|
+
result = await trace(
|
|
209
|
+
TraceOptions(name="my-operation", user_id="123"),
|
|
210
|
+
my_async_function,
|
|
211
|
+
arg1, arg2,
|
|
212
|
+
)
|
|
213
|
+
print(f"Result: {result.result}, Trace ID: {result.trace_id}")
|
|
214
|
+
"""
|
|
215
|
+
if isinstance(options, str):
|
|
216
|
+
options = TraceOptions(name=options)
|
|
217
|
+
|
|
218
|
+
tracer = otel_trace.get_tracer("agentmark")
|
|
219
|
+
|
|
220
|
+
with tracer.start_as_current_span(options.name) as span:
|
|
221
|
+
_set_agentmark_attributes(span, options)
|
|
222
|
+
|
|
223
|
+
span_ctx = span.get_span_context()
|
|
224
|
+
ctx = TraceContext(
|
|
225
|
+
trace_id=format(span_ctx.trace_id, "032x"),
|
|
226
|
+
span_id=format(span_ctx.span_id, "016x"),
|
|
227
|
+
_span=span,
|
|
228
|
+
_tracer=tracer,
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
try:
|
|
232
|
+
result = await fn(*args, **kwargs)
|
|
233
|
+
span.set_status(StatusCode.OK)
|
|
234
|
+
return TraceResult(result=result, trace_id=ctx.trace_id)
|
|
235
|
+
except Exception as e:
|
|
236
|
+
span.set_status(StatusCode.ERROR, str(e))
|
|
237
|
+
raise
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@asynccontextmanager
|
|
241
|
+
async def trace_context(
|
|
242
|
+
options: TraceOptions | str,
|
|
243
|
+
) -> AsyncIterator[TraceContext]:
|
|
244
|
+
"""Create a trace as an async context manager.
|
|
245
|
+
|
|
246
|
+
Alternative API for when you want to use a context manager instead
|
|
247
|
+
of passing a function.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
options: Trace options or just a name string.
|
|
251
|
+
|
|
252
|
+
Yields:
|
|
253
|
+
TraceContext with trace_id, span_id, and utility methods.
|
|
254
|
+
|
|
255
|
+
Example:
|
|
256
|
+
async with trace_context(TraceOptions(name="my-operation")) as ctx:
|
|
257
|
+
print(f"Trace ID: {ctx.trace_id}")
|
|
258
|
+
ctx.set_attribute("custom_key", "value")
|
|
259
|
+
result = await my_async_function()
|
|
260
|
+
"""
|
|
261
|
+
if isinstance(options, str):
|
|
262
|
+
options = TraceOptions(name=options)
|
|
263
|
+
|
|
264
|
+
tracer = otel_trace.get_tracer("agentmark")
|
|
265
|
+
|
|
266
|
+
with tracer.start_as_current_span(options.name) as span:
|
|
267
|
+
_set_agentmark_attributes(span, options)
|
|
268
|
+
|
|
269
|
+
span_ctx = span.get_span_context()
|
|
270
|
+
ctx = TraceContext(
|
|
271
|
+
trace_id=format(span_ctx.trace_id, "032x"),
|
|
272
|
+
span_id=format(span_ctx.span_id, "016x"),
|
|
273
|
+
_span=span,
|
|
274
|
+
_tracer=tracer,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
yield ctx
|
|
279
|
+
span.set_status(StatusCode.OK)
|
|
280
|
+
except Exception as e:
|
|
281
|
+
span.set_status(StatusCode.ERROR, str(e))
|
|
282
|
+
raise
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
# ---------------------------------------------------------------------------
|
|
286
|
+
# Renamed aliases (trace → span rename)
|
|
287
|
+
# ---------------------------------------------------------------------------
|
|
288
|
+
SpanOptions = TraceOptions
|
|
289
|
+
SpanContext = TraceContext
|
|
290
|
+
SpanResult = TraceResult
|
|
291
|
+
span = trace
|
|
292
|
+
span_context = trace_context
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
# ---------------------------------------------------------------------------
|
|
296
|
+
# Sync wrapper for span_context (used by orchestrator in sync code)
|
|
297
|
+
# ---------------------------------------------------------------------------
|
|
298
|
+
from contextlib import contextmanager # noqa: E402
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@contextmanager
|
|
302
|
+
def span_context_sync(options: TraceOptions | str):
|
|
303
|
+
"""Synchronous context manager wrapper around span_context.
|
|
304
|
+
|
|
305
|
+
Creates an OTEL span without requiring async. The span is started
|
|
306
|
+
and stopped synchronously; the yielded context has the same API
|
|
307
|
+
as the async version (set_attribute, add_event, etc.) but without
|
|
308
|
+
the async child-span helper.
|
|
309
|
+
"""
|
|
310
|
+
if isinstance(options, str):
|
|
311
|
+
options = TraceOptions(name=options)
|
|
312
|
+
|
|
313
|
+
tracer = otel_trace.get_tracer("agentmark")
|
|
314
|
+
|
|
315
|
+
with tracer.start_as_current_span(options.name) as otel_span:
|
|
316
|
+
_set_agentmark_attributes(otel_span, options)
|
|
317
|
+
|
|
318
|
+
span_ctx = otel_span.get_span_context()
|
|
319
|
+
ctx = TraceContext(
|
|
320
|
+
trace_id=format(span_ctx.trace_id, "032x"),
|
|
321
|
+
span_id=format(span_ctx.span_id, "016x"),
|
|
322
|
+
_span=otel_span,
|
|
323
|
+
_tracer=tracer,
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
try:
|
|
327
|
+
yield ctx
|
|
328
|
+
otel_span.set_status(StatusCode.OK)
|
|
329
|
+
except Exception as e:
|
|
330
|
+
otel_span.set_status(StatusCode.ERROR, str(e))
|
|
331
|
+
raise
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentmark-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: AgentMark SDK for Python - Tracing and Observability
|
|
5
|
+
Author-email: AgentMark <support@agentmark.co>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: agentmark,llm,observability,opentelemetry,tracing
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Requires-Dist: httpx>=0.25
|
|
18
|
+
Requires-Dist: opentelemetry-api>=1.20
|
|
19
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http>=1.20
|
|
20
|
+
Requires-Dist: opentelemetry-sdk>=1.20
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
agentmark_sdk/__init__.py,sha256=6QecsmFhFtg6H6S7OphsWXzGQLSL9ehMbDI4js_W6Jk,1637
|
|
2
|
+
agentmark_sdk/config.py,sha256=M2oZBF4vQPTMtSiYJtLwQmUdrXGqq55Jcceo8uNXeNY,300
|
|
3
|
+
agentmark_sdk/decorator.py,sha256=Xvb74xFqnxtOjlvLwidSe576Fl4u1vNjpUB7JYBY7mw,6330
|
|
4
|
+
agentmark_sdk/sampler.py,sha256=bXqhw7ma3mINh7TkLneAeyQz4bYwiJj4TLZJpOsHx5E,1897
|
|
5
|
+
agentmark_sdk/sdk.py,sha256=G2jzdWaWIwcjC6lagilgyN4FwnEYp-HpR3ouuLvDxjY,7319
|
|
6
|
+
agentmark_sdk/serialize.py,sha256=OzzMmYpllnxtYh7Cs-n-Dx3jpyeQMv0wPucUQhjz6dc,1053
|
|
7
|
+
agentmark_sdk/trace.py,sha256=_Z-BUlHiPCAhVihTBQ19FTNDAFqeCEq4isMJ7Ebtn3A,10682
|
|
8
|
+
agentmark_sdk-0.1.0.dist-info/METADATA,sha256=YDmXZxeN63AQj9VoBXWwX6GPqb3boOx6Ph1zzNA1pQc,997
|
|
9
|
+
agentmark_sdk-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
10
|
+
agentmark_sdk-0.1.0.dist-info/RECORD,,
|