ioa-observe-sdk 1.0.15__py3-none-any.whl → 1.0.17__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.
- ioa_observe/sdk/__init__.py +3 -3
- ioa_observe/sdk/client/client.py +3 -3
- ioa_observe/sdk/decorators/__init__.py +2 -2
- ioa_observe/sdk/decorators/base.py +2 -2
- ioa_observe/sdk/instrumentations/a2a.py +84 -31
- ioa_observe/sdk/instrumentations/mcp.py +494 -0
- ioa_observe/sdk/instrumentations/slim.py +376 -128
- ioa_observe/sdk/tracing/tracing.py +214 -9
- ioa_observe/sdk/tracing/transform_span.py +210 -0
- ioa_observe/sdk/utils/const.py +7 -0
- {ioa_observe_sdk-1.0.15.dist-info → ioa_observe_sdk-1.0.17.dist-info}/METADATA +3 -1
- {ioa_observe_sdk-1.0.15.dist-info → ioa_observe_sdk-1.0.17.dist-info}/RECORD +15 -13
- {ioa_observe_sdk-1.0.15.dist-info → ioa_observe_sdk-1.0.17.dist-info}/WHEEL +0 -0
- {ioa_observe_sdk-1.0.15.dist-info → ioa_observe_sdk-1.0.17.dist-info}/licenses/LICENSE.md +0 -0
- {ioa_observe_sdk-1.0.15.dist-info → ioa_observe_sdk-1.0.17.dist-info}/top_level.txt +0 -0
ioa_observe/sdk/__init__.py
CHANGED
|
@@ -89,7 +89,7 @@ class Observe:
|
|
|
89
89
|
|
|
90
90
|
if (
|
|
91
91
|
observe_sync_enabled
|
|
92
|
-
and api_endpoint.find("observe.com") != -1
|
|
92
|
+
and api_endpoint.find("agntcy-observe.com") != -1
|
|
93
93
|
and api_key
|
|
94
94
|
and (exporter is None)
|
|
95
95
|
and (processor is None)
|
|
@@ -112,13 +112,13 @@ class Observe:
|
|
|
112
112
|
if (
|
|
113
113
|
not exporter
|
|
114
114
|
and not processor
|
|
115
|
-
and api_endpoint == "https://api.observe.com"
|
|
115
|
+
and api_endpoint == "https://api.agntcy-observe.com"
|
|
116
116
|
and not api_key
|
|
117
117
|
):
|
|
118
118
|
print(
|
|
119
119
|
Fore.RED
|
|
120
120
|
+ "Error: Missing observe API key,"
|
|
121
|
-
+ " go to https://app.observe.com/settings/api-keys to create one"
|
|
121
|
+
+ " go to https://app.agntcy-observe.com/settings/api-keys to create one"
|
|
122
122
|
)
|
|
123
123
|
print("Set the OBSERVE_API_KEY environment variable to the key")
|
|
124
124
|
print(Fore.RESET)
|
ioa_observe/sdk/client/client.py
CHANGED
|
@@ -27,7 +27,7 @@ class Client:
|
|
|
27
27
|
self,
|
|
28
28
|
api_key: str,
|
|
29
29
|
app_name: str = sys.argv[0],
|
|
30
|
-
api_endpoint: str = "https://api.observe.com",
|
|
30
|
+
api_endpoint: str = "https://api.agntcy-observe.com",
|
|
31
31
|
):
|
|
32
32
|
"""
|
|
33
33
|
Initialize a new observe client.
|
|
@@ -35,13 +35,13 @@ class Client:
|
|
|
35
35
|
Args:
|
|
36
36
|
api_key (str): Your observe API key
|
|
37
37
|
app_name (Optional[str], optional): The name of your application. Defaults to sys.argv[0].
|
|
38
|
-
api_endpoint (Optional[str], optional): Custom API endpoint. Defaults to https://api.observe.com.
|
|
38
|
+
api_endpoint (Optional[str], optional): Custom API endpoint. Defaults to https://api.agntcy-observe.com.
|
|
39
39
|
"""
|
|
40
40
|
if not api_key or not api_key.strip():
|
|
41
41
|
raise ValueError("API key is required")
|
|
42
42
|
|
|
43
43
|
self.app_name = app_name
|
|
44
|
-
self.api_endpoint = api_endpoint or "https://api.observe.com"
|
|
44
|
+
self.api_endpoint = api_endpoint or "https://api.agntcy-observe.com"
|
|
45
45
|
self.api_key = api_key
|
|
46
46
|
self._http = HTTPClient(
|
|
47
47
|
base_url=self.api_endpoint, api_key=self.api_key, version=__version__
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Copyright AGNTCY Contributors (https://github.com/agntcy)
|
|
2
2
|
# SPDX-License-Identifier: Apache-2.0
|
|
3
3
|
import inspect
|
|
4
|
-
from typing import Optional, Union, TypeVar, Callable,
|
|
4
|
+
from typing import Optional, Union, TypeVar, Callable, Any
|
|
5
5
|
|
|
6
6
|
from typing_extensions import ParamSpec
|
|
7
7
|
|
|
@@ -14,7 +14,7 @@ from ioa_observe.sdk.utils.const import ObserveSpanKindValues
|
|
|
14
14
|
|
|
15
15
|
P = ParamSpec("P")
|
|
16
16
|
R = TypeVar("R")
|
|
17
|
-
F = TypeVar("F", bound=Callable[
|
|
17
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
def task(
|
|
@@ -7,7 +7,7 @@ import traceback
|
|
|
7
7
|
from functools import wraps
|
|
8
8
|
import os
|
|
9
9
|
import types
|
|
10
|
-
from typing import Optional, TypeVar, Callable,
|
|
10
|
+
from typing import Optional, TypeVar, Callable, Any
|
|
11
11
|
import inspect
|
|
12
12
|
|
|
13
13
|
from ioa_observe.sdk.decorators.helpers import (
|
|
@@ -53,7 +53,7 @@ from ioa_observe.sdk.metrics.agent import topology_dynamism, determinism_score
|
|
|
53
53
|
P = ParamSpec("P")
|
|
54
54
|
|
|
55
55
|
R = TypeVar("R")
|
|
56
|
-
F = TypeVar("F", bound=Callable[
|
|
56
|
+
F = TypeVar("F", bound=Callable[..., Any])
|
|
57
57
|
|
|
58
58
|
|
|
59
59
|
def _is_json_size_valid(json_str: str) -> bool:
|
|
@@ -14,7 +14,7 @@ from ioa_observe.sdk import TracerWrapper
|
|
|
14
14
|
from ioa_observe.sdk.client import kv_store
|
|
15
15
|
from ioa_observe.sdk.tracing import set_session_id, get_current_traceparent
|
|
16
16
|
|
|
17
|
-
_instruments = ("a2a-sdk >= 0.
|
|
17
|
+
_instruments = ("a2a-sdk >= 0.3.0",)
|
|
18
18
|
_global_tracer = None
|
|
19
19
|
_kv_lock = threading.RLock() # Add thread-safety for kv_store operations
|
|
20
20
|
|
|
@@ -34,15 +34,14 @@ class A2AInstrumentor(BaseInstrumentor):
|
|
|
34
34
|
if importlib.util.find_spec("a2a") is None:
|
|
35
35
|
raise ImportError("No module named 'a2a-sdk'. Please install it first.")
|
|
36
36
|
|
|
37
|
-
# Instrument
|
|
37
|
+
# Instrument client send_message
|
|
38
38
|
from a2a.client import A2AClient
|
|
39
39
|
|
|
40
40
|
original_send_message = A2AClient.send_message
|
|
41
41
|
|
|
42
42
|
@functools.wraps(original_send_message)
|
|
43
|
-
async def instrumented_send_message(
|
|
44
|
-
|
|
45
|
-
):
|
|
43
|
+
async def instrumented_send_message(self, request, *args, **kwargs):
|
|
44
|
+
# Put context into A2A message metadata instead of HTTP headers
|
|
46
45
|
with _global_tracer.start_as_current_span("a2a.send_message"):
|
|
47
46
|
traceparent = get_current_traceparent()
|
|
48
47
|
session_id = None
|
|
@@ -50,47 +49,101 @@ class A2AInstrumentor(BaseInstrumentor):
|
|
|
50
49
|
session_id = kv_store.get(f"execution.{traceparent}")
|
|
51
50
|
if session_id:
|
|
52
51
|
kv_store.set(f"execution.{traceparent}", session_id)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
|
|
53
|
+
# Ensure metadata dict exists
|
|
54
|
+
try:
|
|
55
|
+
md = getattr(request.params, "metadata", None)
|
|
56
|
+
except AttributeError:
|
|
57
|
+
md = None
|
|
58
|
+
metadata = md if isinstance(md, dict) else {}
|
|
59
|
+
|
|
60
|
+
observe_meta = dict(metadata.get("observe", {}))
|
|
61
|
+
|
|
62
|
+
# Inject W3C trace context + baggage into observe_meta
|
|
63
|
+
TraceContextTextMapPropagator().inject(carrier=observe_meta)
|
|
64
|
+
W3CBaggagePropagator().inject(carrier=observe_meta)
|
|
65
|
+
|
|
66
|
+
if traceparent:
|
|
67
|
+
observe_meta["traceparent"] = traceparent
|
|
58
68
|
if session_id:
|
|
59
|
-
|
|
69
|
+
observe_meta["session_id"] = session_id
|
|
60
70
|
baggage.set_baggage(f"execution.{traceparent}", session_id)
|
|
61
|
-
http_kwargs["headers"] = headers
|
|
62
|
-
return await original_send_message(self, request, http_kwargs=http_kwargs)
|
|
63
71
|
|
|
64
|
-
|
|
72
|
+
metadata["observe"] = observe_meta
|
|
73
|
+
|
|
74
|
+
# Write back metadata (pydantic models are mutable by default in v2)
|
|
75
|
+
try:
|
|
76
|
+
request.params.metadata = metadata
|
|
77
|
+
except Exception:
|
|
78
|
+
# Fallback
|
|
79
|
+
request = request.model_copy(
|
|
80
|
+
update={
|
|
81
|
+
"params": request.params.model_copy(
|
|
82
|
+
update={"metadata": metadata}
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Call through without transport-specific kwargs
|
|
88
|
+
return await original_send_message(self, request, *args, **kwargs)
|
|
65
89
|
|
|
66
90
|
A2AClient.send_message = instrumented_send_message
|
|
67
91
|
|
|
92
|
+
# Instrument server handler
|
|
68
93
|
from a2a.server.request_handlers import DefaultRequestHandler
|
|
69
94
|
|
|
70
95
|
original_server_on_message_send = DefaultRequestHandler.on_message_send
|
|
71
96
|
|
|
72
97
|
@functools.wraps(original_server_on_message_send)
|
|
73
|
-
async def
|
|
74
|
-
#
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
98
|
+
async def instrumented_on_message_send(self, params, context):
|
|
99
|
+
# Read context from A2A message metadata (transport-agnostic)
|
|
100
|
+
try:
|
|
101
|
+
metadata = getattr(params, "metadata", {}) or {}
|
|
102
|
+
except Exception:
|
|
103
|
+
metadata = {}
|
|
104
|
+
|
|
105
|
+
carrier = {}
|
|
106
|
+
observe_meta = metadata.get("observe", {}) or {}
|
|
107
|
+
# Accept keys we inject
|
|
108
|
+
for k in ("traceparent", "baggage", "session_id"):
|
|
109
|
+
if k in observe_meta:
|
|
110
|
+
carrier[k] = observe_meta[k]
|
|
111
|
+
|
|
112
|
+
token = None
|
|
113
|
+
if carrier.get("traceparent"):
|
|
114
|
+
# Extract and attach parent context
|
|
84
115
|
ctx = TraceContextTextMapPropagator().extract(carrier=carrier)
|
|
85
116
|
ctx = W3CBaggagePropagator().extract(carrier=carrier, context=ctx)
|
|
117
|
+
try:
|
|
118
|
+
from opentelemetry import context as otel_ctx
|
|
119
|
+
|
|
120
|
+
token = otel_ctx.attach(ctx)
|
|
121
|
+
except Exception:
|
|
122
|
+
token = None
|
|
123
|
+
|
|
124
|
+
session_id = observe_meta.get("session_id")
|
|
86
125
|
if session_id and session_id != "None":
|
|
87
|
-
set_session_id(session_id, traceparent=traceparent)
|
|
88
|
-
kv_store.set(f"execution.{traceparent}", session_id)
|
|
89
|
-
return await original_server_on_message_send(self, params, context)
|
|
126
|
+
set_session_id(session_id, traceparent=carrier.get("traceparent"))
|
|
127
|
+
kv_store.set(f"execution.{carrier.get('traceparent')}", session_id)
|
|
90
128
|
|
|
91
|
-
|
|
129
|
+
try:
|
|
130
|
+
return await original_server_on_message_send(self, params, context)
|
|
131
|
+
finally:
|
|
132
|
+
if token is not None:
|
|
133
|
+
try:
|
|
134
|
+
otel_ctx.detach(token)
|
|
135
|
+
except Exception:
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
DefaultRequestHandler.on_message_send = instrumented_on_message_send
|
|
139
|
+
|
|
140
|
+
# from a2a.client import A2AClient
|
|
141
|
+
|
|
142
|
+
# A2AClient.send_message = instrumented_send_message
|
|
143
|
+
|
|
144
|
+
# from a2a.server.request_handlers import DefaultRequestHandler
|
|
92
145
|
|
|
93
|
-
DefaultRequestHandler.on_message_send
|
|
146
|
+
# original_server_on_message_send = DefaultRequestHandler.on_message_send
|
|
94
147
|
|
|
95
148
|
def _uninstrument(self, **kwargs):
|
|
96
149
|
import importlib
|
|
@@ -103,7 +156,7 @@ class A2AInstrumentor(BaseInstrumentor):
|
|
|
103
156
|
|
|
104
157
|
A2AClient.send_message = A2AClient.send_message.__wrapped__
|
|
105
158
|
|
|
106
|
-
# Uninstrument
|
|
159
|
+
# Uninstrument server handler
|
|
107
160
|
from a2a.server.request_handlers import DefaultRequestHandler
|
|
108
161
|
|
|
109
162
|
DefaultRequestHandler.on_message_send = (
|